Skip to content

Enable external DF SDK #839

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

Merged
merged 41 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8ab426e
enable external sdk
davidmrdavid Jul 12, 2022
c90c070
improve readability of PR
davidmrdavid Jul 18, 2022
f2e291c
improve readability further
davidmrdavid Jul 18, 2022
419ae37
clean up PR further
davidmrdavid Jul 18, 2022
049c4f7
further cleanup
davidmrdavid Jul 18, 2022
1297298
remove unecessary file
davidmrdavid Jul 18, 2022
0d1cc12
increase JSON depth limit
davidmrdavid Jul 18, 2022
88dcc98
respond to some PR feedback
davidmrdavid Jul 22, 2022
1ac8d49
patch tests
davidmrdavid Jul 22, 2022
d7fe44f
mock HasExternalSDK in tests
davidmrdavid Jul 25, 2022
6f2d654
add return-false to mocked hasExternalDurableSDK
davidmrdavid Jul 25, 2022
65913c3
fully mock hasExternalSDK
davidmrdavid Jul 25, 2022
00bbf2e
turn IsOrchestrationFailure into constant string in string-resource file
davidmrdavid Jul 25, 2022
1bff0cb
make project build again
davidmrdavid Jul 25, 2022
a793c75
clean up PR
davidmrdavid Jul 25, 2022
635a341
add missing mock for HasExternalDurableSDK
davidmrdavid Jul 25, 2022
d4b5084
add exception-message to InvalidOperation
davidmrdavid Jul 25, 2022
3f3dea6
change exception type on initializeBindings
davidmrdavid Jul 25, 2022
8dd5ebb
apply feedback. comments are pending
davidmrdavid Dec 3, 2022
4668b8d
add comments
davidmrdavid Dec 3, 2022
765b963
fix typo
davidmrdavid Dec 3, 2022
e1273d3
Merge branch 'dev' of https://github.com/Azure/azure-functions-powers…
davidmrdavid Dec 3, 2022
b24b6ec
refactor code, add logging
davidmrdavid Dec 6, 2022
7e98f7d
trim strings resource file
davidmrdavid Dec 6, 2022
71070b5
remove unused strings
davidmrdavid Dec 6, 2022
42cdca6
hide external SDK logic behind feature flag
davidmrdavid Dec 7, 2022
73fcc5e
refactor to facilitate testing
davidmrdavid Dec 8, 2022
cb2a17c
pass tests
davidmrdavid Dec 9, 2022
9ab5b09
Add unit tests
davidmrdavid Dec 9, 2022
5511464
minor edits
davidmrdavid Dec 9, 2022
bf4b2f2
remove unecessary log
davidmrdavid Dec 9, 2022
7c24933
apply feedback
davidmrdavid Dec 12, 2022
dee9ad9
remove module names from strings file
davidmrdavid Dec 13, 2022
6f7fa8a
modify log to mention PSWorkerPath explicitly
davidmrdavid Dec 13, 2022
889f65f
replace importing external DF SDK for simply validating it is in session
davidmrdavid Dec 14, 2022
a1e5993
remove unused strings
davidmrdavid Dec 14, 2022
8fe3d36
remove typos
davidmrdavid Dec 14, 2022
6b36ee1
patch resource strings
davidmrdavid Dec 14, 2022
6e1e97f
apply feedback
davidmrdavid Dec 16, 2022
8e0e103
update error message to specify that DF SDK should only be imported i…
davidmrdavid Jan 6, 2023
e85c19e
simplify instruction to import external SDK
davidmrdavid Jan 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/DurableSDK/ExternalInvoker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
{
using System;
using System.Collections;
using System.Management.Automation;

// Contract for the orchestration invoker in the external Durable Functions SDK
internal class ExternalInvoker : IExternalOrchestrationInvoker
{
private readonly Func<PowerShell, object> _externalSDKInvokerFunction;

public ExternalInvoker(Func<PowerShell, object> invokerFunction)
{
_externalSDKInvokerFunction = invokerFunction;
}

// Invokes an orchestration using the external Durable SDK
public Hashtable Invoke(IPowerShellServices powerShellServices)
{
return (Hashtable)_externalSDKInvokerFunction.Invoke(powerShellServices.GetPowerShell());
}
}
}
16 changes: 16 additions & 0 deletions src/DurableSDK/IExternalOrchestrationInvoker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
{
using System.Collections;

// Contract interface for the orchestration invoker in the external Durable Functions SDK
internal interface IExternalOrchestrationInvoker
{
// Invokes an orchestration using the external Durable SDK
Hashtable Invoke(IPowerShellServices powerShellServices);
}
}
1 change: 1 addition & 0 deletions src/DurableSDK/IOrchestrationInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
internal interface IOrchestrationInvoker
{
Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh);
void SetExternalInvoker(IExternalOrchestrationInvoker externalInvoker);
}
}
15 changes: 14 additions & 1 deletion src/DurableSDK/IPowerShellServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@

namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
{
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
using System;
using System.Management.Automation;

internal interface IPowerShellServices
{
PowerShell GetPowerShell();

bool HasExternalDurableSDK();

bool isExternalDurableSdkLoaded();

void EnableExternalDurableSDK();

void SetDurableClient(object durableClient);

void SetOrchestrationContext(OrchestrationContext orchestrationContext);
OrchestrationBindingInfo SetOrchestrationContext(ParameterBinding context, out IExternalOrchestrationInvoker externalInvoker);

void ClearOrchestrationContext();

void TracePipelineObject();

void AddParameter(string name, object value);

IAsyncResult BeginInvoke(PSDataCollection<object> output);

void EndInvoke(IAsyncResult asyncResult);
Expand Down
112 changes: 78 additions & 34 deletions src/DurableSDK/OrchestrationInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,58 +11,97 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
using System.Linq;
using System.Management.Automation;

using PowerShellWorker.Utility;
using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions;
using Microsoft.Azure.Functions.PowerShellWorker.Utility;

internal class OrchestrationInvoker : IOrchestrationInvoker
{
public Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh)
private IExternalOrchestrationInvoker externalInvoker;

public Hashtable Invoke(
OrchestrationBindingInfo orchestrationBindingInfo,
IPowerShellServices powerShellServices)
{
try
{
var outputBuffer = new PSDataCollection<object>();
var context = orchestrationBindingInfo.Context;
if (powerShellServices.HasExternalDurableSDK())
{
return InvokeExternalDurableSDK(powerShellServices);
}
return InvokeInternalDurableSDK(orchestrationBindingInfo, powerShellServices);
}
catch (Exception ex)
{
ex.Data.Add(Utils.IsOrchestrationFailureKey, true);
throw;
}
finally
{
powerShellServices.ClearStreamsAndCommands();
}
}

public Hashtable InvokeExternalDurableSDK(IPowerShellServices powerShellServices)
{
return externalInvoker.Invoke(powerShellServices);
}

public Hashtable InvokeInternalDurableSDK(
OrchestrationBindingInfo orchestrationBindingInfo,
IPowerShellServices powerShellServices)
{
var outputBuffer = new PSDataCollection<object>();
var context = orchestrationBindingInfo.Context;

// context.History should never be null when initializing CurrentUtcDateTime
var orchestrationStart = context.History.First(
e => e.EventType == HistoryEventType.OrchestratorStarted);
context.CurrentUtcDateTime = orchestrationStart.Timestamp.ToUniversalTime();

// context.History should never be null when initializing CurrentUtcDateTime
var orchestrationStart = context.History.First(
e => e.EventType == HistoryEventType.OrchestratorStarted);
context.CurrentUtcDateTime = orchestrationStart.Timestamp.ToUniversalTime();
// Marks the first OrchestratorStarted event as processed
orchestrationStart.IsProcessed = true;

// Marks the first OrchestratorStarted event as processed
orchestrationStart.IsProcessed = true;

var asyncResult = pwsh.BeginInvoke(outputBuffer);
// Finish initializing the Function invocation
powerShellServices.AddParameter(orchestrationBindingInfo.ParameterName, context);
powerShellServices.TracePipelineObject();

var (shouldStop, actions) =
orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle);
var asyncResult = powerShellServices.BeginInvoke(outputBuffer);

if (shouldStop)
var (shouldStop, actions) =
orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle);

if (shouldStop)
{
// The orchestration function should be stopped and restarted
powerShellServices.StopInvoke();
return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus);
}
else
{
try
{
// The orchestration function should be stopped and restarted
pwsh.StopInvoke();
return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus);
// The orchestration function completed
powerShellServices.EndInvoke(asyncResult);
var result = CreateReturnValueFromFunctionOutput(outputBuffer);
return CreateOrchestrationResult(isDone: true, actions, output: result, context.CustomStatus);
}
else
catch (Exception e)
{
try
{
// The orchestration function completed
pwsh.EndInvoke(asyncResult);
var result = FunctionReturnValueBuilder.CreateReturnValueFromFunctionOutput(outputBuffer);
return CreateOrchestrationResult(isDone: true, actions, output: result, context.CustomStatus);
}
catch (Exception e)
{
// The orchestrator code has thrown an unhandled exception:
// this should be treated as an entire orchestration failure
throw new OrchestrationFailureException(actions, context.CustomStatus, e);
}
// The orchestrator code has thrown an unhandled exception:
// this should be treated as an entire orchestration failure
throw new OrchestrationFailureException(actions, context.CustomStatus, e);
}
}
finally
}

public static object CreateReturnValueFromFunctionOutput(IList<object> pipelineItems)
{
if (pipelineItems == null || pipelineItems.Count <= 0)
{
pwsh.ClearStreamsAndCommands();
return null;
Comment on lines -64 to +101
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for clarity: the pwsh.ClearStreamsAndCommand() call here didn't use to be in the CreateReturnValueFromFunctionOutput method. The implementation of this method has not changed at all, it's just that the diff is acting up and still showing code from the previous Invoke implementation.

}

return pipelineItems.Count == 1 ? pipelineItems[0] : pipelineItems.ToArray();
}

private static Hashtable CreateOrchestrationResult(
Expand All @@ -74,5 +113,10 @@ private static Hashtable CreateOrchestrationResult(
var orchestrationMessage = new OrchestrationMessage(isDone, actions, output, customStatus);
return new Hashtable { { AzFunctionInfo.DollarReturn, orchestrationMessage } };
}

public void SetExternalInvoker(IExternalOrchestrationInvoker externalInvoker)
{
this.externalInvoker = externalInvoker;
}
}
}
Loading