From 60e65d46d91d0f1f33b1f08d8cdae3d5e68edfab Mon Sep 17 00:00:00 2001 From: David Justo Date: Fri, 28 Jan 2022 19:20:04 -0800 Subject: [PATCH 1/2] separate DF SDK classes from DF worker classes fix typo remove TODOs remove commented-out code --- .../Actions/ActionType.cs | 0 .../Actions/CallActivityAction.cs | 0 .../Actions/CallActivityWithRetryAction.cs | 0 .../Actions/CreateDurableTimerAction.cs | 0 .../Actions/ExternalEventAction.cs | 0 .../Actions/OrchestrationAction.cs | 0 .../ActivityFailureException.cs | 0 .../Commands/InvokeDurableActivityCommand.cs | 4 ++-- .../Commands/SetDurableCustomStatusCommand.cs | 0 .../SetFunctionInvocationContextCommand.cs | 0 ...tartDurableExternalEventListenerCommand.cs | 0 .../Commands/StartDurableTimerCommand.cs | 0 .../Commands/StopDurableTimerTaskCommand.cs | 0 .../Commands/WaitDurableTaskCommand.cs | 0 .../CurrentUtcDateTimeUpdater.cs | 0 .../DurableActivityErrorHandler.cs | 0 .../DurableTaskHandler.cs | 0 src/{Durable => DurableSDK}/HistoryEvent.cs | 0 .../HistoryEventType.cs | 0 .../IOrchestrationInvoker.cs | 0 .../IPowerShellServices.cs | 0 .../OrchestrationActionCollector.cs | 0 .../OrchestrationBindingInfo.cs | 0 .../OrchestrationContext.cs | 0 .../OrchestrationFailureException.cs | 0 .../OrchestrationInvoker.cs | 0 .../OrchestrationMessage.cs | 0 .../PowerShellServices.cs | 0 src/{Durable => DurableSDK}/RetryOptions.cs | 0 src/{Durable => DurableSDK}/RetryProcessor.cs | 0 .../Tasks/ActivityInvocationTask.cs | 1 + .../Tasks/DurableTask.cs | 0 .../Tasks/DurableTimerTask.cs | 0 .../Tasks/ExternalEventTask.cs | 0 .../DurableBindings.cs | 2 +- .../DurableController.cs | 1 + .../DurableFunctionInfo.cs | 2 +- .../DurableFunctionInfoFactory.cs | 2 +- .../DurableFunctionType.cs | 2 +- src/FunctionInfo.cs | 2 +- src/RequestProcessor.cs | 2 +- test/E2E/TestFunctionApp/profile.ps1 | 22 +++++++++++++++++++ test/Unit/Durable/DurableControllerTests.cs | 1 + .../DurableFunctionInfoFactoryTests.cs | 2 +- .../Unit/PowerShell/PowerShellManagerTests.cs | 1 + 45 files changed, 35 insertions(+), 9 deletions(-) rename src/{Durable => DurableSDK}/Actions/ActionType.cs (100%) rename src/{Durable => DurableSDK}/Actions/CallActivityAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/CallActivityWithRetryAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/CreateDurableTimerAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/ExternalEventAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/OrchestrationAction.cs (100%) rename src/{Durable => DurableSDK}/ActivityFailureException.cs (100%) rename src/{Durable => DurableSDK}/Commands/InvokeDurableActivityCommand.cs (93%) rename src/{Durable => DurableSDK}/Commands/SetDurableCustomStatusCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/SetFunctionInvocationContextCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/StartDurableExternalEventListenerCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/StartDurableTimerCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/StopDurableTimerTaskCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/WaitDurableTaskCommand.cs (100%) rename src/{Durable => DurableSDK}/CurrentUtcDateTimeUpdater.cs (100%) rename src/{Durable => DurableSDK}/DurableActivityErrorHandler.cs (100%) rename src/{Durable => DurableSDK}/DurableTaskHandler.cs (100%) rename src/{Durable => DurableSDK}/HistoryEvent.cs (100%) rename src/{Durable => DurableSDK}/HistoryEventType.cs (100%) rename src/{Durable => DurableSDK}/IOrchestrationInvoker.cs (100%) rename src/{Durable => DurableSDK}/IPowerShellServices.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationActionCollector.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationBindingInfo.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationContext.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationFailureException.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationInvoker.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationMessage.cs (100%) rename src/{Durable => DurableSDK}/PowerShellServices.cs (100%) rename src/{Durable => DurableSDK}/RetryOptions.cs (100%) rename src/{Durable => DurableSDK}/RetryProcessor.cs (100%) rename src/{Durable => DurableSDK}/Tasks/ActivityInvocationTask.cs (98%) rename src/{Durable => DurableSDK}/Tasks/DurableTask.cs (100%) rename src/{Durable => DurableSDK}/Tasks/DurableTimerTask.cs (100%) rename src/{Durable => DurableSDK}/Tasks/ExternalEventTask.cs (100%) rename src/{Durable => DurableWorker}/DurableBindings.cs (95%) rename src/{Durable => DurableWorker}/DurableController.cs (98%) rename src/{Durable => DurableWorker}/DurableFunctionInfo.cs (92%) rename src/{Durable => DurableWorker}/DurableFunctionInfoFactory.cs (96%) rename src/{Durable => DurableWorker}/DurableFunctionType.cs (80%) create mode 100644 test/E2E/TestFunctionApp/profile.ps1 diff --git a/src/Durable/Actions/ActionType.cs b/src/DurableSDK/Actions/ActionType.cs similarity index 100% rename from src/Durable/Actions/ActionType.cs rename to src/DurableSDK/Actions/ActionType.cs diff --git a/src/Durable/Actions/CallActivityAction.cs b/src/DurableSDK/Actions/CallActivityAction.cs similarity index 100% rename from src/Durable/Actions/CallActivityAction.cs rename to src/DurableSDK/Actions/CallActivityAction.cs diff --git a/src/Durable/Actions/CallActivityWithRetryAction.cs b/src/DurableSDK/Actions/CallActivityWithRetryAction.cs similarity index 100% rename from src/Durable/Actions/CallActivityWithRetryAction.cs rename to src/DurableSDK/Actions/CallActivityWithRetryAction.cs diff --git a/src/Durable/Actions/CreateDurableTimerAction.cs b/src/DurableSDK/Actions/CreateDurableTimerAction.cs similarity index 100% rename from src/Durable/Actions/CreateDurableTimerAction.cs rename to src/DurableSDK/Actions/CreateDurableTimerAction.cs diff --git a/src/Durable/Actions/ExternalEventAction.cs b/src/DurableSDK/Actions/ExternalEventAction.cs similarity index 100% rename from src/Durable/Actions/ExternalEventAction.cs rename to src/DurableSDK/Actions/ExternalEventAction.cs diff --git a/src/Durable/Actions/OrchestrationAction.cs b/src/DurableSDK/Actions/OrchestrationAction.cs similarity index 100% rename from src/Durable/Actions/OrchestrationAction.cs rename to src/DurableSDK/Actions/OrchestrationAction.cs diff --git a/src/Durable/ActivityFailureException.cs b/src/DurableSDK/ActivityFailureException.cs similarity index 100% rename from src/Durable/ActivityFailureException.cs rename to src/DurableSDK/ActivityFailureException.cs diff --git a/src/Durable/Commands/InvokeDurableActivityCommand.cs b/src/DurableSDK/Commands/InvokeDurableActivityCommand.cs similarity index 93% rename from src/Durable/Commands/InvokeDurableActivityCommand.cs rename to src/DurableSDK/Commands/InvokeDurableActivityCommand.cs index ba9b429f..4868b62c 100644 --- a/src/Durable/Commands/InvokeDurableActivityCommand.cs +++ b/src/DurableSDK/Commands/InvokeDurableActivityCommand.cs @@ -43,10 +43,10 @@ protected override void EndProcessing() { var privateData = (Hashtable)MyInvocation.MyCommand.Module.PrivateData; var context = (OrchestrationContext)privateData[SetFunctionInvocationContextCommand.ContextKey]; - var loadedFunctions = FunctionLoader.GetLoadedFunctions(); + // var loadedFunctions = FunctionLoader.GetLoadedFunctions(); var task = new ActivityInvocationTask(FunctionName, Input, RetryOptions); - ActivityInvocationTask.ValidateTask(task, loadedFunctions); + // ActivityInvocationTask.ValidateTask(task, loadedFunctions); _durableTaskHandler.StopAndInitiateDurableTaskOrReplay( task, context, NoWait.IsPresent, diff --git a/src/Durable/Commands/SetDurableCustomStatusCommand.cs b/src/DurableSDK/Commands/SetDurableCustomStatusCommand.cs similarity index 100% rename from src/Durable/Commands/SetDurableCustomStatusCommand.cs rename to src/DurableSDK/Commands/SetDurableCustomStatusCommand.cs diff --git a/src/Durable/Commands/SetFunctionInvocationContextCommand.cs b/src/DurableSDK/Commands/SetFunctionInvocationContextCommand.cs similarity index 100% rename from src/Durable/Commands/SetFunctionInvocationContextCommand.cs rename to src/DurableSDK/Commands/SetFunctionInvocationContextCommand.cs diff --git a/src/Durable/Commands/StartDurableExternalEventListenerCommand.cs b/src/DurableSDK/Commands/StartDurableExternalEventListenerCommand.cs similarity index 100% rename from src/Durable/Commands/StartDurableExternalEventListenerCommand.cs rename to src/DurableSDK/Commands/StartDurableExternalEventListenerCommand.cs diff --git a/src/Durable/Commands/StartDurableTimerCommand.cs b/src/DurableSDK/Commands/StartDurableTimerCommand.cs similarity index 100% rename from src/Durable/Commands/StartDurableTimerCommand.cs rename to src/DurableSDK/Commands/StartDurableTimerCommand.cs diff --git a/src/Durable/Commands/StopDurableTimerTaskCommand.cs b/src/DurableSDK/Commands/StopDurableTimerTaskCommand.cs similarity index 100% rename from src/Durable/Commands/StopDurableTimerTaskCommand.cs rename to src/DurableSDK/Commands/StopDurableTimerTaskCommand.cs diff --git a/src/Durable/Commands/WaitDurableTaskCommand.cs b/src/DurableSDK/Commands/WaitDurableTaskCommand.cs similarity index 100% rename from src/Durable/Commands/WaitDurableTaskCommand.cs rename to src/DurableSDK/Commands/WaitDurableTaskCommand.cs diff --git a/src/Durable/CurrentUtcDateTimeUpdater.cs b/src/DurableSDK/CurrentUtcDateTimeUpdater.cs similarity index 100% rename from src/Durable/CurrentUtcDateTimeUpdater.cs rename to src/DurableSDK/CurrentUtcDateTimeUpdater.cs diff --git a/src/Durable/DurableActivityErrorHandler.cs b/src/DurableSDK/DurableActivityErrorHandler.cs similarity index 100% rename from src/Durable/DurableActivityErrorHandler.cs rename to src/DurableSDK/DurableActivityErrorHandler.cs diff --git a/src/Durable/DurableTaskHandler.cs b/src/DurableSDK/DurableTaskHandler.cs similarity index 100% rename from src/Durable/DurableTaskHandler.cs rename to src/DurableSDK/DurableTaskHandler.cs diff --git a/src/Durable/HistoryEvent.cs b/src/DurableSDK/HistoryEvent.cs similarity index 100% rename from src/Durable/HistoryEvent.cs rename to src/DurableSDK/HistoryEvent.cs diff --git a/src/Durable/HistoryEventType.cs b/src/DurableSDK/HistoryEventType.cs similarity index 100% rename from src/Durable/HistoryEventType.cs rename to src/DurableSDK/HistoryEventType.cs diff --git a/src/Durable/IOrchestrationInvoker.cs b/src/DurableSDK/IOrchestrationInvoker.cs similarity index 100% rename from src/Durable/IOrchestrationInvoker.cs rename to src/DurableSDK/IOrchestrationInvoker.cs diff --git a/src/Durable/IPowerShellServices.cs b/src/DurableSDK/IPowerShellServices.cs similarity index 100% rename from src/Durable/IPowerShellServices.cs rename to src/DurableSDK/IPowerShellServices.cs diff --git a/src/Durable/OrchestrationActionCollector.cs b/src/DurableSDK/OrchestrationActionCollector.cs similarity index 100% rename from src/Durable/OrchestrationActionCollector.cs rename to src/DurableSDK/OrchestrationActionCollector.cs diff --git a/src/Durable/OrchestrationBindingInfo.cs b/src/DurableSDK/OrchestrationBindingInfo.cs similarity index 100% rename from src/Durable/OrchestrationBindingInfo.cs rename to src/DurableSDK/OrchestrationBindingInfo.cs diff --git a/src/Durable/OrchestrationContext.cs b/src/DurableSDK/OrchestrationContext.cs similarity index 100% rename from src/Durable/OrchestrationContext.cs rename to src/DurableSDK/OrchestrationContext.cs diff --git a/src/Durable/OrchestrationFailureException.cs b/src/DurableSDK/OrchestrationFailureException.cs similarity index 100% rename from src/Durable/OrchestrationFailureException.cs rename to src/DurableSDK/OrchestrationFailureException.cs diff --git a/src/Durable/OrchestrationInvoker.cs b/src/DurableSDK/OrchestrationInvoker.cs similarity index 100% rename from src/Durable/OrchestrationInvoker.cs rename to src/DurableSDK/OrchestrationInvoker.cs diff --git a/src/Durable/OrchestrationMessage.cs b/src/DurableSDK/OrchestrationMessage.cs similarity index 100% rename from src/Durable/OrchestrationMessage.cs rename to src/DurableSDK/OrchestrationMessage.cs diff --git a/src/Durable/PowerShellServices.cs b/src/DurableSDK/PowerShellServices.cs similarity index 100% rename from src/Durable/PowerShellServices.cs rename to src/DurableSDK/PowerShellServices.cs diff --git a/src/Durable/RetryOptions.cs b/src/DurableSDK/RetryOptions.cs similarity index 100% rename from src/Durable/RetryOptions.cs rename to src/DurableSDK/RetryOptions.cs diff --git a/src/Durable/RetryProcessor.cs b/src/DurableSDK/RetryProcessor.cs similarity index 100% rename from src/Durable/RetryProcessor.cs rename to src/DurableSDK/RetryProcessor.cs diff --git a/src/Durable/Tasks/ActivityInvocationTask.cs b/src/DurableSDK/Tasks/ActivityInvocationTask.cs similarity index 98% rename from src/Durable/Tasks/ActivityInvocationTask.cs rename to src/DurableSDK/Tasks/ActivityInvocationTask.cs index 87bf61c2..fc3921cc 100644 --- a/src/Durable/Tasks/ActivityInvocationTask.cs +++ b/src/DurableSDK/Tasks/ActivityInvocationTask.cs @@ -16,6 +16,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks using Microsoft.Azure.Functions.PowerShellWorker; using Microsoft.Azure.Functions.PowerShellWorker.Durable; using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; public class ActivityInvocationTask : DurableTask { diff --git a/src/Durable/Tasks/DurableTask.cs b/src/DurableSDK/Tasks/DurableTask.cs similarity index 100% rename from src/Durable/Tasks/DurableTask.cs rename to src/DurableSDK/Tasks/DurableTask.cs diff --git a/src/Durable/Tasks/DurableTimerTask.cs b/src/DurableSDK/Tasks/DurableTimerTask.cs similarity index 100% rename from src/Durable/Tasks/DurableTimerTask.cs rename to src/DurableSDK/Tasks/DurableTimerTask.cs diff --git a/src/Durable/Tasks/ExternalEventTask.cs b/src/DurableSDK/Tasks/ExternalEventTask.cs similarity index 100% rename from src/Durable/Tasks/ExternalEventTask.cs rename to src/DurableSDK/Tasks/ExternalEventTask.cs diff --git a/src/Durable/DurableBindings.cs b/src/DurableWorker/DurableBindings.cs similarity index 95% rename from src/Durable/DurableBindings.cs rename to src/DurableWorker/DurableBindings.cs index 6bdf2468..d46a4adc 100644 --- a/src/Durable/DurableBindings.cs +++ b/src/DurableWorker/DurableBindings.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { using System; diff --git a/src/Durable/DurableController.cs b/src/DurableWorker/DurableController.cs similarity index 98% rename from src/Durable/DurableController.cs rename to src/DurableWorker/DurableController.cs index 6b1f31bb..7b7f1b4e 100644 --- a/src/Durable/DurableController.cs +++ b/src/DurableWorker/DurableController.cs @@ -16,6 +16,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable using WebJobs.Script.Grpc.Messages; using PowerShellWorker.Utility; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; /// /// The main entry point for durable functions support. diff --git a/src/Durable/DurableFunctionInfo.cs b/src/DurableWorker/DurableFunctionInfo.cs similarity index 92% rename from src/Durable/DurableFunctionInfo.cs rename to src/DurableWorker/DurableFunctionInfo.cs index a245ac67..8665cfe8 100644 --- a/src/Durable/DurableFunctionInfo.cs +++ b/src/DurableWorker/DurableFunctionInfo.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { internal class DurableFunctionInfo { diff --git a/src/Durable/DurableFunctionInfoFactory.cs b/src/DurableWorker/DurableFunctionInfoFactory.cs similarity index 96% rename from src/Durable/DurableFunctionInfoFactory.cs rename to src/DurableWorker/DurableFunctionInfoFactory.cs index 3e65b9f7..0cc08d27 100644 --- a/src/Durable/DurableFunctionInfoFactory.cs +++ b/src/DurableWorker/DurableFunctionInfoFactory.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { using System.Linq; diff --git a/src/Durable/DurableFunctionType.cs b/src/DurableWorker/DurableFunctionType.cs similarity index 80% rename from src/Durable/DurableFunctionType.cs rename to src/DurableWorker/DurableFunctionType.cs index 73feb504..2ecacc17 100644 --- a/src/Durable/DurableFunctionType.cs +++ b/src/DurableWorker/DurableFunctionType.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { internal enum DurableFunctionType { diff --git a/src/FunctionInfo.cs b/src/FunctionInfo.cs index 2aea62ec..091eecde 100644 --- a/src/FunctionInfo.cs +++ b/src/FunctionInfo.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker { - using Durable; + using DurableWorker; /// /// This type represents the metadata of an Azure PowerShell Function. diff --git a/src/RequestProcessor.cs b/src/RequestProcessor.cs index 6aabd3f3..93a1271f 100644 --- a/src/RequestProcessor.cs +++ b/src/RequestProcessor.cs @@ -13,7 +13,7 @@ using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; using Microsoft.Azure.Functions.PowerShellWorker.Utility; using Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement; -using Microsoft.Azure.Functions.PowerShellWorker.Durable; +using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; namespace Microsoft.Azure.Functions.PowerShellWorker diff --git a/test/E2E/TestFunctionApp/profile.ps1 b/test/E2E/TestFunctionApp/profile.ps1 new file mode 100644 index 00000000..9afdf1da --- /dev/null +++ b/test/E2E/TestFunctionApp/profile.ps1 @@ -0,0 +1,22 @@ +# Azure Functions profile.ps1 +# +# This profile.ps1 will get executed every "cold start" of your Function App. +# "cold start" occurs when: +# +# * A Function App starts up for the very first time +# * A Function App starts up after being de-allocated due to inactivity +# +# You can define helper functions, run commands, or specify environment variables +# NOTE: any variables defined that are not environment variables will get reset after the first execution + +# Authenticate with Azure PowerShell using MSI. +# Remove this if you are not planning on using MSI or Azure PowerShell. +if ($env:MSI_SECRET) { + Disable-AzContextAutosave -Scope Process | Out-Null + Connect-AzAccount -Identity +} + +# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. +# Enable-AzureRmAlias + +# You can also define functions or aliases that can be referenced in any of your PowerShell functions. \ No newline at end of file diff --git a/test/Unit/Durable/DurableControllerTests.cs b/test/Unit/Durable/DurableControllerTests.cs index 68531f7c..5ec3244c 100644 --- a/test/Unit/Durable/DurableControllerTests.cs +++ b/test/Unit/Durable/DurableControllerTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.Durable using System.Collections.ObjectModel; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using Microsoft.Azure.Functions.PowerShellWorker.Durable; using Microsoft.Azure.Functions.PowerShellWorker.Utility; using Newtonsoft.Json; diff --git a/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs b/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs index 09cea266..2c898316 100644 --- a/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs +++ b/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.Durable using Xunit; - using Microsoft.Azure.Functions.PowerShellWorker.Durable; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; public class DurableFunctionInfoFactoryTests { diff --git a/test/Unit/PowerShell/PowerShellManagerTests.cs b/test/Unit/PowerShell/PowerShellManagerTests.cs index 5d4faf42..36588611 100644 --- a/test/Unit/PowerShell/PowerShellManagerTests.cs +++ b/test/Unit/PowerShell/PowerShellManagerTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test using System.Collections.ObjectModel; using System.Management.Automation; using Microsoft.Azure.Functions.PowerShellWorker.Durable; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using Newtonsoft.Json; internal class TestUtils From 9a2724e961ab7249668837b4510d50ef96974817 Mon Sep 17 00:00:00 2001 From: David Justo Date: Fri, 28 Jan 2022 19:20:04 -0800 Subject: [PATCH 2/2] separate DF SDK classes from DF worker classes fix typo DurableSDK now compiles by itself Allow ExternalSDK to handle orchestration document next steps allow external SDK to set the user-code's input. Still need to refactor this logic for the worker to continue working with old SDK add import module supress traces avoid nullptr pass tests fix E2E tests develop E2E tests Enabled external durable client (#765) Co-authored-by: Michael Peng bindings work conditional binding intialization conditional import Added exception handling logic Revert durableController name to durableFunctionsUtils Ensure unit tests are functioning properly Corrected unit test names Turned repeated variables in unit tests into static members Fixed issue with building the worker Fix E2E test Fixed unit test setup Fixed another unit test setup Remove string representation of booleans patch e2e test remove typo in toString Update PowerShell language worker pipelines (#750) * Install .Net to a global location * Remove .Net installation tasks * Update install .Net 6 task * Update Windows image to use windows-latest Make throughput warning message visible for tooling diagnosis (#757) Update grpc.tools to version 2.43.0 Update Google.Protobuf.Tools to version 3.19.4 Revert "Update Google.Protobuf.Tools to version 3.19.4" This reverts commit bcbd02231ba2a1cc242183442123ff781f009483. Revert "Update grpc.tools to version 2.43.0" This reverts commit ccb323aa31240137ec0a8bef1b5e17bccf4ae6c8. Update Google.Protobuf to 3.19.4 and grpc.tools to 2.43.0 (#762) * Update grpc.tools to version 2.43.0 * Update Google.Protobuf.Tools to version 3.19.4 Switch from Grpc.Core to Grpc.Net.Client (#758) * Upgraded protobuf versions and removed Grpc.Core dependency * Updated channel and option types used * Change channel credentials * Added http prefix to url * Add valid URL check and explicitly include credentials Update pipeline logic to generate the SBOM for release builds (#767) separate DF SDK classes from DF worker classes fix typo DurableSDK now compiles by itself Allow ExternalSDK to handle orchestration document next steps allow external SDK to set the user-code's input. Still need to refactor this logic for the worker to continue working with old SDK add import module supress traces avoid nullptr pass tests fix E2E tests develop E2E tests Enabled external durable client (#765) Co-authored-by: Michael Peng bindings work conditional binding intialization conditional import Added exception handling logic Revert durableController name to durableFunctionsUtils Ensure unit tests are functioning properly Corrected unit test names Turned repeated variables in unit tests into static members Fixed issue with building the worker Fix E2E test Fixed unit test setup Fixed another unit test setup Remove string representation of booleans patch e2e test remove typo in toString Update PowerShell language worker pipelines (#750) * Install .Net to a global location * Remove .Net installation tasks * Update install .Net 6 task * Update Windows image to use windows-latest Make throughput warning message visible for tooling diagnosis (#757) Update grpc.tools to version 2.43.0 Update Google.Protobuf.Tools to version 3.19.4 Revert "Update Google.Protobuf.Tools to version 3.19.4" This reverts commit bcbd02231ba2a1cc242183442123ff781f009483. Revert "Update grpc.tools to version 2.43.0" This reverts commit ccb323aa31240137ec0a8bef1b5e17bccf4ae6c8. Update Google.Protobuf to 3.19.4 and grpc.tools to 2.43.0 (#762) * Update grpc.tools to version 2.43.0 * Update Google.Protobuf.Tools to version 3.19.4 Switch from Grpc.Core to Grpc.Net.Client (#758) * Upgraded protobuf versions and removed Grpc.Core dependency * Updated channel and option types used * Change channel credentials * Added http prefix to url * Add valid URL check and explicitly include credentials Update pipeline logic to generate the SBOM for release builds (#767) Return results from Start-DurableExternalEventListener (#685) (#753) Co-authored-by: Greg Roll add e2e test for GetTaskResult parametrize test patch new e2e test patch external contrib fix typo in test comment changes Adds IExternalInvoker (#776) * Define a contract for the external invoker * Remove extraneous comments and variables rename hasOrchestrationContext to hasInitializedDurableFunction remove outdated TODO comment remove now unused function - CreateOrchestrationBindingInfo Allow worker to read results directly from the external SDK (#777) comment out external SDK path --- azure-pipelines-e2e-integration-tests.yml | 14 +- azure-pipelines.yml | 24 +-- src/Durable/OrchestrationInvoker.cs | 78 -------- src/Durable/PowerShellServices.cs | 74 -------- .../Actions/ActionType.cs | 0 .../Actions/CallActivityAction.cs | 0 .../Actions/CallActivityWithRetryAction.cs | 0 .../Actions/CreateDurableTimerAction.cs | 0 .../Actions/ExternalEventAction.cs | 0 .../Actions/OrchestrationAction.cs | 0 .../ActivityFailureException.cs | 0 .../Commands/GetDurableTaskResult.cs | 36 ++++ .../Commands/InvokeDurableActivityCommand.cs | 2 - .../Commands/SetDurableCustomStatusCommand.cs | 0 .../SetFunctionInvocationContextCommand.cs | 1 + ...tartDurableExternalEventListenerCommand.cs | 0 .../Commands/StartDurableTimerCommand.cs | 0 .../Commands/StopDurableTimerTaskCommand.cs | 0 .../Commands/WaitDurableTaskCommand.cs | 0 .../CurrentUtcDateTimeUpdater.cs | 0 .../DurableActivityErrorHandler.cs | 0 .../DurableTaskHandler.cs | 63 ++++++- src/DurableSDK/ExternalInvoker.cs | 26 +++ src/{Durable => DurableSDK}/HistoryEvent.cs | 0 .../HistoryEventType.cs | 0 src/DurableSDK/IExternalInvoker.cs | 16 ++ .../IOrchestrationInvoker.cs | 1 + .../IPowerShellServices.cs | 11 +- .../OrchestrationActionCollector.cs | 1 + .../OrchestrationBindingInfo.cs | 0 .../OrchestrationContext.cs | 4 +- .../OrchestrationFailureException.cs | 0 src/DurableSDK/OrchestrationInvoker.cs | 123 +++++++++++++ .../OrchestrationMessage.cs | 0 src/DurableSDK/PowerShellExtensions.cs | 69 +++++++ src/DurableSDK/PowerShellServices.cs | 168 ++++++++++++++++++ src/{Durable => DurableSDK}/RetryOptions.cs | 0 src/{Durable => DurableSDK}/RetryProcessor.cs | 0 .../Tasks/ActivityInvocationTask.cs | 31 +--- .../Tasks/DurableTask.cs | 4 +- .../Tasks/DurableTimerTask.cs | 4 +- .../Tasks/ExternalEventTask.cs | 6 +- .../DurableBindings.cs | 2 +- .../DurableController.cs | 50 ++---- .../DurableFunctionInfo.cs | 5 +- .../DurableFunctionInfoFactory.cs | 2 +- .../DurableFunctionType.cs | 2 +- src/FunctionInfo.cs | 2 +- src/Messaging/MessagingStream.cs | 18 +- ...ft.Azure.Functions.PowerShellWorker.csproj | 4 +- ...soft.Azure.Functions.PowerShellWorker.psd1 | 1 + ...soft.Azure.Functions.PowerShellWorker.psm1 | 29 ++- src/PowerShell/PowerShellManager.cs | 53 +++--- src/PowerShell/PowerShellManagerPool.cs | 2 +- src/RequestProcessor.cs | 2 +- src/Utility/Utils.cs | 1 + .../DurableEndToEndTests.cs | 106 +++++++++++ .../function.json | 24 +++ .../run.ps1 | 9 + .../function.json | 9 + .../run.ps1 | 8 + .../function.json | 9 + .../DurableOrchestratorGetTaskResult/run.ps1 | 8 + .../function.json | 9 + .../DurableOrchestratorRaiseEvent/run.ps1 | 9 + test/E2E/TestFunctionApp/profile.ps1 | 22 +++ .../Durable/ActivityInvocationTaskTests.cs | 57 +----- test/Unit/Durable/DurableControllerTests.cs | 112 +++++------- .../DurableFunctionInfoFactoryTests.cs | 2 +- .../Unit/Durable/OrchestrationInvokerTests.cs | 8 + .../Unit/PowerShell/PowerShellManagerTests.cs | 1 + tools/helper.psm1 | 20 +-- tools/protobuf.tools.csproj | 4 +- 73 files changed, 909 insertions(+), 437 deletions(-) delete mode 100644 src/Durable/OrchestrationInvoker.cs delete mode 100644 src/Durable/PowerShellServices.cs rename src/{Durable => DurableSDK}/Actions/ActionType.cs (100%) rename src/{Durable => DurableSDK}/Actions/CallActivityAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/CallActivityWithRetryAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/CreateDurableTimerAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/ExternalEventAction.cs (100%) rename src/{Durable => DurableSDK}/Actions/OrchestrationAction.cs (100%) rename src/{Durable => DurableSDK}/ActivityFailureException.cs (100%) create mode 100644 src/DurableSDK/Commands/GetDurableTaskResult.cs rename src/{Durable => DurableSDK}/Commands/InvokeDurableActivityCommand.cs (93%) rename src/{Durable => DurableSDK}/Commands/SetDurableCustomStatusCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/SetFunctionInvocationContextCommand.cs (97%) rename src/{Durable => DurableSDK}/Commands/StartDurableExternalEventListenerCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/StartDurableTimerCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/StopDurableTimerTaskCommand.cs (100%) rename src/{Durable => DurableSDK}/Commands/WaitDurableTaskCommand.cs (100%) rename src/{Durable => DurableSDK}/CurrentUtcDateTimeUpdater.cs (100%) rename src/{Durable => DurableSDK}/DurableActivityErrorHandler.cs (100%) rename src/{Durable => DurableSDK}/DurableTaskHandler.cs (76%) create mode 100644 src/DurableSDK/ExternalInvoker.cs rename src/{Durable => DurableSDK}/HistoryEvent.cs (100%) rename src/{Durable => DurableSDK}/HistoryEventType.cs (100%) create mode 100644 src/DurableSDK/IExternalInvoker.cs rename src/{Durable => DurableSDK}/IOrchestrationInvoker.cs (86%) rename src/{Durable => DurableSDK}/IPowerShellServices.cs (64%) rename src/{Durable => DurableSDK}/OrchestrationActionCollector.cs (98%) rename src/{Durable => DurableSDK}/OrchestrationBindingInfo.cs (100%) rename src/{Durable => DurableSDK}/OrchestrationContext.cs (91%) rename src/{Durable => DurableSDK}/OrchestrationFailureException.cs (100%) create mode 100644 src/DurableSDK/OrchestrationInvoker.cs rename src/{Durable => DurableSDK}/OrchestrationMessage.cs (100%) create mode 100644 src/DurableSDK/PowerShellExtensions.cs create mode 100644 src/DurableSDK/PowerShellServices.cs rename src/{Durable => DurableSDK}/RetryOptions.cs (100%) rename src/{Durable => DurableSDK}/RetryProcessor.cs (100%) rename src/{Durable => DurableSDK}/Tasks/ActivityInvocationTask.cs (62%) rename src/{Durable => DurableSDK}/Tasks/DurableTask.cs (89%) rename src/{Durable => DurableSDK}/Tasks/DurableTimerTask.cs (97%) rename src/{Durable => DurableSDK}/Tasks/ExternalEventTask.cs (88%) rename src/{Durable => DurableWorker}/DurableBindings.cs (95%) rename src/{Durable => DurableWorker}/DurableController.cs (64%) rename src/{Durable => DurableWorker}/DurableFunctionInfo.cs (84%) rename src/{Durable => DurableWorker}/DurableFunctionInfoFactory.cs (96%) rename src/{Durable => DurableWorker}/DurableFunctionType.cs (80%) create mode 100644 test/E2E/TestFunctionApp/DurableClientOrchContextProperties/function.json create mode 100644 test/E2E/TestFunctionApp/DurableClientOrchContextProperties/run.ps1 create mode 100644 test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/function.json create mode 100644 test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/run.ps1 create mode 100644 test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/function.json create mode 100644 test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/run.ps1 create mode 100644 test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/function.json create mode 100644 test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/run.ps1 create mode 100644 test/E2E/TestFunctionApp/profile.ps1 diff --git a/azure-pipelines-e2e-integration-tests.yml b/azure-pipelines-e2e-integration-tests.yml index 680b2e38..4df22ec5 100644 --- a/azure-pipelines-e2e-integration-tests.yml +++ b/azure-pipelines-e2e-integration-tests.yml @@ -11,22 +11,16 @@ strategy: linux: imageName: 'ubuntu-latest' windows: - imageName: 'vs2017-win2016' + imageName: 'windows-latest' pool: vmImage: $(imageName) steps: - pwsh: | - Invoke-WebRequest 'https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1' -OutFile 'dotnet-install.ps1' - ./dotnet-install.ps1 -InstallDir "$env:ProgramFiles/dotnet" -Version "6.0.100" -Channel 'release' - displayName: 'Install the .Net version used by the Core Tools for Windows' - condition: eq( variables['Agent.OS'], 'Windows_NT' ) - -- bash: | - curl -sSL https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh | bash /dev/stdin -v '6.0.100' -c 'release' --install-dir /usr/share/dotnet - displayName: 'Install the .Net version used by the Core Tools for Linux' - condition: eq( variables['Agent.OS'], 'Linux' ) + Import-Module "./tools/helper.psm1" -Force + Install-Dotnet + displayName: 'Install .NET 6.0' - pwsh: ./test/E2E/Start-E2ETest.ps1 -UseCoreToolsBuildFromIntegrationTests env: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f3ee4db4..c1256877 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -43,32 +43,14 @@ steps: - pwsh: | $ErrorActionPreference = "Stop" - if ($isReleaseBuild) - { - ./build.ps1 -Clean -Configuration Release -BuildNumber "$(buildNumber)" -AddSBOM -SBOMUtilSASUrl $env:SBOMUtilSASUrl - } - else - { - ./build.ps1 -Clean -Configuration Release -BuildNumber "$(buildNumber)" - } + $shouldAddSBOM = [bool]"$(IsReleaseBuild)" + + ./build.ps1 -Clean -Configuration Release -BuildNumber "$(buildNumber)" -AddSBOM:$shouldAddSBOM -SBOMUtilSASUrl "$(SBOMUtilSASUrl)" displayName: 'Build worker code' - env: - SBOMUtilSASUrl: $(SBOMUtilSASUrl) - pwsh: ./build.ps1 -NoBuild -Test displayName: 'Running UnitTest' -- pwsh: | - Invoke-WebRequest 'https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1' -OutFile 'dotnet-install.ps1' - ./dotnet-install.ps1 -InstallDir "$env:ProgramFiles/dotnet" -Version "6.0.100" -Channel 'release' - displayName: 'Install the .Net version used by the Core Tools for Windows' - condition: eq( variables['Agent.OS'], 'Windows_NT' ) - -- bash: | - curl -sSL https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh | bash /dev/stdin -v '6.0.100' -c 'release' --install-dir /usr/share/dotnet - displayName: 'Install the .Net version used by the Core Tools for Linux' - condition: eq( variables['Agent.OS'], 'Linux' ) - - pwsh: ./test/E2E/Start-E2ETest.ps1 env: AzureWebJobsStorage: $(AzureWebJobsStorage) diff --git a/src/Durable/OrchestrationInvoker.cs b/src/Durable/OrchestrationInvoker.cs deleted file mode 100644 index fef557ba..00000000 --- a/src/Durable/OrchestrationInvoker.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// 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.Collections.Generic; - using System.Linq; - using System.Management.Automation; - - using PowerShellWorker.Utility; - using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions; - - internal class OrchestrationInvoker : IOrchestrationInvoker - { - public Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh) - { - try - { - var outputBuffer = new PSDataCollection(); - 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(); - - // Marks the first OrchestratorStarted event as processed - orchestrationStart.IsProcessed = true; - - var asyncResult = pwsh.BeginInvoke(outputBuffer); - - var (shouldStop, actions) = - orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle); - - if (shouldStop) - { - // The orchestration function should be stopped and restarted - pwsh.StopInvoke(); - return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus); - } - else - { - 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); - } - } - } - finally - { - pwsh.ClearStreamsAndCommands(); - } - } - - private static Hashtable CreateOrchestrationResult( - bool isDone, - List> actions, - object output, - object customStatus) - { - var orchestrationMessage = new OrchestrationMessage(isDone, actions, output, customStatus); - return new Hashtable { { AzFunctionInfo.DollarReturn, orchestrationMessage } }; - } - } -} diff --git a/src/Durable/PowerShellServices.cs b/src/Durable/PowerShellServices.cs deleted file mode 100644 index 0efb681d..00000000 --- a/src/Durable/PowerShellServices.cs +++ /dev/null @@ -1,74 +0,0 @@ -// -// 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.Management.Automation; - using PowerShell; - - internal class PowerShellServices : IPowerShellServices - { - private const string SetFunctionInvocationContextCommand = - "Microsoft.Azure.Functions.PowerShellWorker\\Set-FunctionInvocationContext"; - - private readonly PowerShell _pwsh; - private bool _hasSetOrchestrationContext = false; - - public PowerShellServices(PowerShell pwsh) - { - _pwsh = pwsh; - } - - public void SetDurableClient(object durableClient) - { - _pwsh.AddCommand(SetFunctionInvocationContextCommand) - .AddParameter("DurableClient", durableClient) - .InvokeAndClearCommands(); - - _hasSetOrchestrationContext = true; - } - - public void SetOrchestrationContext(OrchestrationContext orchestrationContext) - { - _pwsh.AddCommand(SetFunctionInvocationContextCommand) - .AddParameter("OrchestrationContext", orchestrationContext) - .InvokeAndClearCommands(); - - _hasSetOrchestrationContext = true; - } - - public void ClearOrchestrationContext() - { - if (_hasSetOrchestrationContext) - { - _pwsh.AddCommand(SetFunctionInvocationContextCommand) - .AddParameter("Clear", true) - .InvokeAndClearCommands(); - } - } - - public IAsyncResult BeginInvoke(PSDataCollection output) - { - return _pwsh.BeginInvoke(input: null, output); - } - - public void EndInvoke(IAsyncResult asyncResult) - { - _pwsh.EndInvoke(asyncResult); - } - - public void StopInvoke() - { - _pwsh.Stop(); - } - - public void ClearStreamsAndCommands() - { - _pwsh.Streams.ClearStreams(); - _pwsh.Commands.Clear(); - } - } -} diff --git a/src/Durable/Actions/ActionType.cs b/src/DurableSDK/Actions/ActionType.cs similarity index 100% rename from src/Durable/Actions/ActionType.cs rename to src/DurableSDK/Actions/ActionType.cs diff --git a/src/Durable/Actions/CallActivityAction.cs b/src/DurableSDK/Actions/CallActivityAction.cs similarity index 100% rename from src/Durable/Actions/CallActivityAction.cs rename to src/DurableSDK/Actions/CallActivityAction.cs diff --git a/src/Durable/Actions/CallActivityWithRetryAction.cs b/src/DurableSDK/Actions/CallActivityWithRetryAction.cs similarity index 100% rename from src/Durable/Actions/CallActivityWithRetryAction.cs rename to src/DurableSDK/Actions/CallActivityWithRetryAction.cs diff --git a/src/Durable/Actions/CreateDurableTimerAction.cs b/src/DurableSDK/Actions/CreateDurableTimerAction.cs similarity index 100% rename from src/Durable/Actions/CreateDurableTimerAction.cs rename to src/DurableSDK/Actions/CreateDurableTimerAction.cs diff --git a/src/Durable/Actions/ExternalEventAction.cs b/src/DurableSDK/Actions/ExternalEventAction.cs similarity index 100% rename from src/Durable/Actions/ExternalEventAction.cs rename to src/DurableSDK/Actions/ExternalEventAction.cs diff --git a/src/Durable/Actions/OrchestrationAction.cs b/src/DurableSDK/Actions/OrchestrationAction.cs similarity index 100% rename from src/Durable/Actions/OrchestrationAction.cs rename to src/DurableSDK/Actions/OrchestrationAction.cs diff --git a/src/Durable/ActivityFailureException.cs b/src/DurableSDK/ActivityFailureException.cs similarity index 100% rename from src/Durable/ActivityFailureException.cs rename to src/DurableSDK/ActivityFailureException.cs diff --git a/src/DurableSDK/Commands/GetDurableTaskResult.cs b/src/DurableSDK/Commands/GetDurableTaskResult.cs new file mode 100644 index 00000000..74f5493b --- /dev/null +++ b/src/DurableSDK/Commands/GetDurableTaskResult.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#pragma warning disable 1591 // Missing XML comment for publicly visible type or member 'member' + +namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Commands +{ + using System.Collections; + using System.Management.Automation; + using Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks; + + [Cmdlet("Get", "DurableTaskResult")] + public class GetDurableTaskResultCommand : PSCmdlet + { + [Parameter(Mandatory = true)] + [ValidateNotNull] + public DurableTask[] Task { get; set; } + + private readonly DurableTaskHandler _durableTaskHandler = new DurableTaskHandler(); + + protected override void EndProcessing() + { + var privateData = (Hashtable)MyInvocation.MyCommand.Module.PrivateData; + var context = (OrchestrationContext)privateData[SetFunctionInvocationContextCommand.ContextKey]; + + _durableTaskHandler.GetTaskResult(Task, context, WriteObject); + } + + protected override void StopProcessing() + { + _durableTaskHandler.Stop(); + } + } +} diff --git a/src/Durable/Commands/InvokeDurableActivityCommand.cs b/src/DurableSDK/Commands/InvokeDurableActivityCommand.cs similarity index 93% rename from src/Durable/Commands/InvokeDurableActivityCommand.cs rename to src/DurableSDK/Commands/InvokeDurableActivityCommand.cs index ba9b429f..4cb8639e 100644 --- a/src/Durable/Commands/InvokeDurableActivityCommand.cs +++ b/src/DurableSDK/Commands/InvokeDurableActivityCommand.cs @@ -43,10 +43,8 @@ protected override void EndProcessing() { var privateData = (Hashtable)MyInvocation.MyCommand.Module.PrivateData; var context = (OrchestrationContext)privateData[SetFunctionInvocationContextCommand.ContextKey]; - var loadedFunctions = FunctionLoader.GetLoadedFunctions(); var task = new ActivityInvocationTask(FunctionName, Input, RetryOptions); - ActivityInvocationTask.ValidateTask(task, loadedFunctions); _durableTaskHandler.StopAndInitiateDurableTaskOrReplay( task, context, NoWait.IsPresent, diff --git a/src/Durable/Commands/SetDurableCustomStatusCommand.cs b/src/DurableSDK/Commands/SetDurableCustomStatusCommand.cs similarity index 100% rename from src/Durable/Commands/SetDurableCustomStatusCommand.cs rename to src/DurableSDK/Commands/SetDurableCustomStatusCommand.cs diff --git a/src/Durable/Commands/SetFunctionInvocationContextCommand.cs b/src/DurableSDK/Commands/SetFunctionInvocationContextCommand.cs similarity index 97% rename from src/Durable/Commands/SetFunctionInvocationContextCommand.cs rename to src/DurableSDK/Commands/SetFunctionInvocationContextCommand.cs index 943e8362..3430be16 100644 --- a/src/Durable/Commands/SetFunctionInvocationContextCommand.cs +++ b/src/DurableSDK/Commands/SetFunctionInvocationContextCommand.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Commands { using System.Collections; using System.Management.Automation; + using Microsoft.PowerShell.Commands; /// /// Set the orchestration context. diff --git a/src/Durable/Commands/StartDurableExternalEventListenerCommand.cs b/src/DurableSDK/Commands/StartDurableExternalEventListenerCommand.cs similarity index 100% rename from src/Durable/Commands/StartDurableExternalEventListenerCommand.cs rename to src/DurableSDK/Commands/StartDurableExternalEventListenerCommand.cs diff --git a/src/Durable/Commands/StartDurableTimerCommand.cs b/src/DurableSDK/Commands/StartDurableTimerCommand.cs similarity index 100% rename from src/Durable/Commands/StartDurableTimerCommand.cs rename to src/DurableSDK/Commands/StartDurableTimerCommand.cs diff --git a/src/Durable/Commands/StopDurableTimerTaskCommand.cs b/src/DurableSDK/Commands/StopDurableTimerTaskCommand.cs similarity index 100% rename from src/Durable/Commands/StopDurableTimerTaskCommand.cs rename to src/DurableSDK/Commands/StopDurableTimerTaskCommand.cs diff --git a/src/Durable/Commands/WaitDurableTaskCommand.cs b/src/DurableSDK/Commands/WaitDurableTaskCommand.cs similarity index 100% rename from src/Durable/Commands/WaitDurableTaskCommand.cs rename to src/DurableSDK/Commands/WaitDurableTaskCommand.cs diff --git a/src/Durable/CurrentUtcDateTimeUpdater.cs b/src/DurableSDK/CurrentUtcDateTimeUpdater.cs similarity index 100% rename from src/Durable/CurrentUtcDateTimeUpdater.cs rename to src/DurableSDK/CurrentUtcDateTimeUpdater.cs diff --git a/src/Durable/DurableActivityErrorHandler.cs b/src/DurableSDK/DurableActivityErrorHandler.cs similarity index 100% rename from src/Durable/DurableActivityErrorHandler.cs rename to src/DurableSDK/DurableActivityErrorHandler.cs diff --git a/src/Durable/DurableTaskHandler.cs b/src/DurableSDK/DurableTaskHandler.cs similarity index 76% rename from src/Durable/DurableTaskHandler.cs rename to src/DurableSDK/DurableTaskHandler.cs index 3981b686..30b5573b 100644 --- a/src/Durable/DurableTaskHandler.cs +++ b/src/DurableSDK/DurableTaskHandler.cs @@ -6,10 +6,12 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable { using System; + using System.Collections; using System.Collections.Generic; + using System.Management.Automation; using System.Threading; using Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks; - using Utility; + using Microsoft.PowerShell.Commands; internal class DurableTaskHandler { @@ -47,6 +49,7 @@ public void StopAndInitiateDurableTaskOrReplay( } completedHistoryEvent.IsProcessed = true; + context.IsReplaying = completedHistoryEvent.IsPlayed; switch (completedHistoryEvent.EventType) { @@ -57,6 +60,13 @@ public void StopAndInitiateDurableTaskOrReplay( output(eventResult); } break; + case HistoryEventType.EventRaised: + var eventRaisedResult = GetEventResult(completedHistoryEvent); + if (eventRaisedResult != null) + { + output(eventRaisedResult); + } + break; case HistoryEventType.TaskFailed: if (retryOptions == null) @@ -76,7 +86,7 @@ public void StopAndInitiateDurableTaskOrReplay( retryOptions.MaxNumberOfAttempts, onSuccess: result => { - output(TypeExtensions.ConvertFromJson(result)); + output(ConvertFromJson(result)); }, onFailure); @@ -126,6 +136,7 @@ public void WaitAll( var allTasksCompleted = completedEvents.Count == tasksToWaitFor.Count; if (allTasksCompleted) { + context.IsReplaying = completedEvents.Count == 0 ? false : completedEvents[0].IsPlayed; CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context); foreach (var completedHistoryEvent in completedEvents) @@ -164,6 +175,7 @@ public void WaitAny( if (scheduledHistoryEvent != null) { scheduledHistoryEvent.IsProcessed = true; + scheduledHistoryEvent.IsPlayed = true; } if (completedHistoryEvent != null) @@ -179,12 +191,14 @@ public void WaitAny( } completedHistoryEvent.IsProcessed = true; + completedHistoryEvent.IsPlayed = true; } } var anyTaskCompleted = completedTasks.Count > 0; if (anyTaskCompleted) { + context.IsReplaying = context.History[firstCompletedHistoryEventIndex].IsPlayed; CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context); // Return a reference to the first completed task output(firstCompletedTask); @@ -195,6 +209,21 @@ public void WaitAny( } } + public void GetTaskResult( + IReadOnlyCollection tasksToQueryResultFor, + OrchestrationContext context, + Action output) + { + foreach (var task in tasksToQueryResultFor) { + var scheduledHistoryEvent = task.GetScheduledHistoryEvent(context, true); + var processedHistoryEvent = task.GetCompletedHistoryEvent(context, scheduledHistoryEvent, true); + if (processedHistoryEvent != null) + { + output(GetEventResult(processedHistoryEvent)); + } + } + } + public void Stop() { _waitForStop.Set(); @@ -206,15 +235,41 @@ private static object GetEventResult(HistoryEvent historyEvent) if (historyEvent.EventType == HistoryEventType.TaskCompleted) { - return TypeExtensions.ConvertFromJson(historyEvent.Result); + return ConvertFromJson(historyEvent.Result); } else if (historyEvent.EventType == HistoryEventType.EventRaised) { - return TypeExtensions.ConvertFromJson(historyEvent.Input); + return ConvertFromJson(historyEvent.Input); } return null; } + public static object ConvertFromJson(string json) + { + object retObj = JsonObject.ConvertFromJson(json, returnHashtable: true, error: out _); + + if (retObj is PSObject psObj) + { + retObj = psObj.BaseObject; + } + + if (retObj is Hashtable hashtable) + { + try + { + // ConvertFromJson returns case-sensitive Hashtable by design -- JSON may contain keys that only differ in case. + // We try casting the Hashtable to a case-insensitive one, but if that fails, we keep using the original one. + retObj = new Hashtable(hashtable, StringComparer.OrdinalIgnoreCase); + } + catch + { + retObj = hashtable; + } + } + + return retObj; + } + private void InitiateAndWaitForStop(OrchestrationContext context) { context.OrchestrationActionCollector.Stop(); diff --git a/src/DurableSDK/ExternalInvoker.cs b/src/DurableSDK/ExternalInvoker.cs new file mode 100644 index 00000000..fa8a31c6 --- /dev/null +++ b/src/DurableSDK/ExternalInvoker.cs @@ -0,0 +1,26 @@ +// +// 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; + + internal class ExternalInvoker : IExternalInvoker + { + private readonly Func _externalSDKInvokerFunction; + + public ExternalInvoker(Func invokerFunction) + { + _externalSDKInvokerFunction = invokerFunction; + } + + public Hashtable Invoke(IPowerShellServices powerShellServices) + { + return (Hashtable)_externalSDKInvokerFunction.Invoke(powerShellServices.GetPowerShell()); + } + } +} diff --git a/src/Durable/HistoryEvent.cs b/src/DurableSDK/HistoryEvent.cs similarity index 100% rename from src/Durable/HistoryEvent.cs rename to src/DurableSDK/HistoryEvent.cs diff --git a/src/Durable/HistoryEventType.cs b/src/DurableSDK/HistoryEventType.cs similarity index 100% rename from src/Durable/HistoryEventType.cs rename to src/DurableSDK/HistoryEventType.cs diff --git a/src/DurableSDK/IExternalInvoker.cs b/src/DurableSDK/IExternalInvoker.cs new file mode 100644 index 00000000..3a703f3d --- /dev/null +++ b/src/DurableSDK/IExternalInvoker.cs @@ -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; + + // Represents a contract for the + internal interface IExternalInvoker + { + // Method to invoke an orchestration using the external Durable SDK + Hashtable Invoke(IPowerShellServices powerShellServices); + } +} diff --git a/src/Durable/IOrchestrationInvoker.cs b/src/DurableSDK/IOrchestrationInvoker.cs similarity index 86% rename from src/Durable/IOrchestrationInvoker.cs rename to src/DurableSDK/IOrchestrationInvoker.cs index 7e80aba3..36011b56 100644 --- a/src/Durable/IOrchestrationInvoker.cs +++ b/src/DurableSDK/IOrchestrationInvoker.cs @@ -10,5 +10,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable internal interface IOrchestrationInvoker { Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh); + void SetExternalInvoker(IExternalInvoker externalInvoker); } } diff --git a/src/Durable/IPowerShellServices.cs b/src/DurableSDK/IPowerShellServices.cs similarity index 64% rename from src/Durable/IPowerShellServices.cs rename to src/DurableSDK/IPowerShellServices.cs index a8cf897b..cdd850bc 100644 --- a/src/Durable/IPowerShellServices.cs +++ b/src/DurableSDK/IPowerShellServices.cs @@ -5,17 +5,26 @@ 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 UseExternalDurableSDK(); + void SetDurableClient(object durableClient); - void SetOrchestrationContext(OrchestrationContext orchestrationContext); + OrchestrationBindingInfo SetOrchestrationContext(ParameterBinding context, out IExternalInvoker externalInvoker); void ClearOrchestrationContext(); + void TracePipelineObject(); + + void AddParameter(string name, object value); + IAsyncResult BeginInvoke(PSDataCollection output); void EndInvoke(IAsyncResult asyncResult); diff --git a/src/Durable/OrchestrationActionCollector.cs b/src/DurableSDK/OrchestrationActionCollector.cs similarity index 98% rename from src/Durable/OrchestrationActionCollector.cs rename to src/DurableSDK/OrchestrationActionCollector.cs index b62fbc4b..1542c2bf 100644 --- a/src/Durable/OrchestrationActionCollector.cs +++ b/src/DurableSDK/OrchestrationActionCollector.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable using System.Threading; using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions; + using Newtonsoft.Json; internal class OrchestrationActionCollector { diff --git a/src/Durable/OrchestrationBindingInfo.cs b/src/DurableSDK/OrchestrationBindingInfo.cs similarity index 100% rename from src/Durable/OrchestrationBindingInfo.cs rename to src/DurableSDK/OrchestrationBindingInfo.cs diff --git a/src/Durable/OrchestrationContext.cs b/src/DurableSDK/OrchestrationContext.cs similarity index 91% rename from src/Durable/OrchestrationContext.cs rename to src/DurableSDK/OrchestrationContext.cs index 0d11acee..27f082db 100644 --- a/src/Durable/OrchestrationContext.cs +++ b/src/DurableSDK/OrchestrationContext.cs @@ -20,13 +20,13 @@ public class OrchestrationContext public object Input { get; internal set; } [DataMember] - internal string InstanceId { get; set; } + public string InstanceId { get; set; } [DataMember] internal string ParentInstanceId { get; set; } [DataMember] - internal bool IsReplaying { get; set; } + public bool IsReplaying { get; set; } [DataMember] internal HistoryEvent[] History { get; set; } diff --git a/src/Durable/OrchestrationFailureException.cs b/src/DurableSDK/OrchestrationFailureException.cs similarity index 100% rename from src/Durable/OrchestrationFailureException.cs rename to src/DurableSDK/OrchestrationFailureException.cs diff --git a/src/DurableSDK/OrchestrationInvoker.cs b/src/DurableSDK/OrchestrationInvoker.cs new file mode 100644 index 00000000..b71116b4 --- /dev/null +++ b/src/DurableSDK/OrchestrationInvoker.cs @@ -0,0 +1,123 @@ +// +// 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.Collections.Generic; + using System.Linq; + using System.Management.Automation; + + using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions; + + internal class OrchestrationInvoker : IOrchestrationInvoker + { + private IExternalInvoker _externalInvoker; + internal static string isOrchestrationFailureKey = "IsOrchestrationFailure"; + + public Hashtable Invoke( + OrchestrationBindingInfo orchestrationBindingInfo, + IPowerShellServices powerShellServices) + { + try + { + if (powerShellServices.UseExternalDurableSDK()) + { + return InvokeExternalDurableSDK(powerShellServices); + } + return InvokeInternalDurableSDK(orchestrationBindingInfo, powerShellServices); + } + catch (Exception ex) + { + ex.Data.Add(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(); + 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(); + + // Marks the first OrchestratorStarted event as processed + orchestrationStart.IsProcessed = true; + + // Finish initializing the Function invocation + powerShellServices.AddParameter(orchestrationBindingInfo.ParameterName, context); + powerShellServices.TracePipelineObject(); + + var asyncResult = powerShellServices.BeginInvoke(outputBuffer); + + var (shouldStop, actions) = + orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle); + + if (shouldStop) + { + // The orchestration function should be stopped and restarted + powerShellServices.StopInvoke(); + // return (Hashtable)orchestrationBindingInfo.Context.OrchestrationActionCollector.output; + return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus); + } + else + { + try + { + // The orchestration function completed + powerShellServices.EndInvoke(asyncResult); + var result = 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); + } + } + } + + public static object CreateReturnValueFromFunctionOutput(IList pipelineItems) + { + if (pipelineItems == null || pipelineItems.Count <= 0) + { + return null; + } + + return pipelineItems.Count == 1 ? pipelineItems[0] : pipelineItems.ToArray(); + } + + private static Hashtable CreateOrchestrationResult( + bool isDone, + List> actions, + object output, + object customStatus) + { + var orchestrationMessage = new OrchestrationMessage(isDone, actions, output, customStatus); + return new Hashtable { { "$return", orchestrationMessage } }; + } + + public void SetExternalInvoker(IExternalInvoker externalInvoker) + { + _externalInvoker = externalInvoker; + } + } +} diff --git a/src/Durable/OrchestrationMessage.cs b/src/DurableSDK/OrchestrationMessage.cs similarity index 100% rename from src/Durable/OrchestrationMessage.cs rename to src/DurableSDK/OrchestrationMessage.cs diff --git a/src/DurableSDK/PowerShellExtensions.cs b/src/DurableSDK/PowerShellExtensions.cs new file mode 100644 index 00000000..a5225188 --- /dev/null +++ b/src/DurableSDK/PowerShellExtensions.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections; +using System.Collections.ObjectModel; + +namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +{ + using System.Management.Automation; + + internal static class PowerShellExtensions + { + public static void InvokeAndClearCommands(this PowerShell pwsh) + { + try + { + pwsh.Invoke(); + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); + } + } + + public static void InvokeAndClearCommands(this PowerShell pwsh, IEnumerable input) + { + try + { + pwsh.Invoke(input); + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); + } + } + + public static Collection InvokeAndClearCommands(this PowerShell pwsh) + { + try + { + var result = pwsh.Invoke(); + return result; + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); + } + } + + public static Collection InvokeAndClearCommands(this PowerShell pwsh, IEnumerable input) + { + try + { + var result = pwsh.Invoke(input); + return result; + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); + } + } + } +} diff --git a/src/DurableSDK/PowerShellServices.cs b/src/DurableSDK/PowerShellServices.cs new file mode 100644 index 00000000..d7433850 --- /dev/null +++ b/src/DurableSDK/PowerShellServices.cs @@ -0,0 +1,168 @@ +// +// 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.ObjectModel; + using System.Linq; + using System.Management.Automation; + using Microsoft.Azure.Functions.PowerShellWorker.Utility; + using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + using Newtonsoft.Json; + + internal class PowerShellServices : IPowerShellServices + { + private readonly string SetFunctionInvocationContextCommand; + private const string ExternalDurableSDKName = "DurableSDK"; + private const string InternalDurableSDKName = "Microsoft.Azure.Functions.PowerShellWorker"; + + private readonly PowerShell _pwsh; + private bool _hasInitializedDurableFunction = false; + private readonly bool _useExternalDurableSDK = false; + + public PowerShellServices(PowerShell pwsh) + { + /* This logic will be commented out until the external SDK is published on the PS Gallery + + // We attempt to import the external SDK upon construction of the PowerShellServices object. + // We maintain the boolean member _useExternalDurableSDK in this object rather than + // DurableController because the expected input and functionality of SetFunctionInvocationContextCommand + // may differ between the internal and external implementations. + + try + { + pwsh.AddCommand(Utils.ImportModuleCmdletInfo) + .AddParameter("Name", ExternalDurableSDKName) + .AddParameter("ErrorAction", ActionPreference.Stop) + .InvokeAndClearCommands(); + _useExternalDurableSDK = true; + } + catch (Exception e) + { + // Check to see if ExternalDurableSDK is among the modules imported or + // available to be imported: if it is, then something went wrong with + // the Import-Module statement and we should throw an Exception. + // Otherwise, we use the InternalDurableSDK + var availableModules = pwsh.AddCommand(Utils.GetModuleCmdletInfo) + .AddParameter("Name", ExternalDurableSDKName) + .InvokeAndClearCommands(); + if (availableModules.Count() > 0) + { + // TODO: evaluate if there is a better error message or exception type to be throwing. + // Ideally, this should never happen. + throw new InvalidOperationException("The external Durable SDK was detected, but unable to be imported.", e); + } + _useExternalDurableSDK = false; + }*/ + _useExternalDurableSDK = false; + + if (_useExternalDurableSDK) + { + SetFunctionInvocationContextCommand = $"{ExternalDurableSDKName}\\Set-FunctionInvocationContext"; + } + else + { + SetFunctionInvocationContextCommand = $"{InternalDurableSDKName}\\Set-FunctionInvocationContext"; + } + _pwsh = pwsh; + } + + public bool UseExternalDurableSDK() + { + return _useExternalDurableSDK; + } + + public PowerShell GetPowerShell() + { + return this._pwsh; + } + + public void SetDurableClient(object durableClient) + { + _pwsh.AddCommand(SetFunctionInvocationContextCommand) + .AddParameter("DurableClient", durableClient) + .InvokeAndClearCommands(); + _hasInitializedDurableFunction = true; + } + + public OrchestrationBindingInfo SetOrchestrationContext( + ParameterBinding context, + out IExternalInvoker externalInvoker) + { + externalInvoker = null; + OrchestrationBindingInfo orchestrationBindingInfo = new OrchestrationBindingInfo( + context.Name, + JsonConvert.DeserializeObject(context.Data.String)); + + if (_useExternalDurableSDK) + { + Collection> output = _pwsh.AddCommand(SetFunctionInvocationContextCommand) + // The external SetFunctionInvocationContextCommand expects a .json string to deserialize + // and writes an invoker function to the output pipeline. + .AddParameter("OrchestrationContext", context.Data.String) + .InvokeAndClearCommands>(); + if (output.Count() == 1) + { + externalInvoker = new ExternalInvoker(output[0]); + } + else + { + throw new InvalidOperationException($"Only a single output was expected for an invocation of {SetFunctionInvocationContextCommand}"); + } + } + else + { + _pwsh.AddCommand(SetFunctionInvocationContextCommand) + .AddParameter("OrchestrationContext", orchestrationBindingInfo.Context) + .InvokeAndClearCommands(); + } + _hasInitializedDurableFunction = true; + return orchestrationBindingInfo; + } + + + public void AddParameter(string name, object value) + { + _pwsh.AddParameter(name, value); + } + + public void ClearOrchestrationContext() + { + if (_hasInitializedDurableFunction) + { + _pwsh.AddCommand(SetFunctionInvocationContextCommand) + .AddParameter("Clear", true) + .InvokeAndClearCommands(); + } + } + + public void TracePipelineObject() + { + _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject"); + } + + public IAsyncResult BeginInvoke(PSDataCollection output) + { + return _pwsh.BeginInvoke(input: null, output); + } + + public void EndInvoke(IAsyncResult asyncResult) + { + _pwsh.EndInvoke(asyncResult); + } + + public void StopInvoke() + { + _pwsh.Stop(); + } + + public void ClearStreamsAndCommands() + { + _pwsh.Streams.ClearStreams(); + _pwsh.Commands.Clear(); + } + } +} diff --git a/src/Durable/RetryOptions.cs b/src/DurableSDK/RetryOptions.cs similarity index 100% rename from src/Durable/RetryOptions.cs rename to src/DurableSDK/RetryOptions.cs diff --git a/src/Durable/RetryProcessor.cs b/src/DurableSDK/RetryProcessor.cs similarity index 100% rename from src/Durable/RetryProcessor.cs rename to src/DurableSDK/RetryProcessor.cs diff --git a/src/Durable/Tasks/ActivityInvocationTask.cs b/src/DurableSDK/Tasks/ActivityInvocationTask.cs similarity index 62% rename from src/Durable/Tasks/ActivityInvocationTask.cs rename to src/DurableSDK/Tasks/ActivityInvocationTask.cs index 87bf61c2..92e42e24 100644 --- a/src/Durable/Tasks/ActivityInvocationTask.cs +++ b/src/DurableSDK/Tasks/ActivityInvocationTask.cs @@ -7,13 +7,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks { - using System; using System.Linq; - using System.Collections.Generic; - - using WebJobs.Script.Grpc.Messages; - - using Microsoft.Azure.Functions.PowerShellWorker; using Microsoft.Azure.Functions.PowerShellWorker.Durable; using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions; @@ -37,15 +31,15 @@ internal ActivityInvocationTask(string functionName, object functionInput) { } - internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context) + internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context, bool processed) { return context.History.FirstOrDefault( e => e.EventType == HistoryEventType.TaskScheduled && e.Name == FunctionName && - !e.IsProcessed); + e.IsProcessed == processed); } - internal override HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent scheduledHistoryEvent) + internal override HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent scheduledHistoryEvent, bool processed) { return scheduledHistoryEvent == null ? null @@ -61,24 +55,5 @@ internal override OrchestrationAction CreateOrchestrationAction() ? new CallActivityAction(FunctionName, Input) : new CallActivityWithRetryAction(FunctionName, Input, RetryOptions); } - - internal static void ValidateTask(ActivityInvocationTask task, IEnumerable loadedFunctions) - { - var functionInfo = loadedFunctions.FirstOrDefault(fi => fi.FuncName == task.FunctionName); - if (functionInfo == null) - { - var message = string.Format(PowerShellWorkerStrings.FunctionNotFound, task.FunctionName); - throw new InvalidOperationException(message); - } - - var activityTriggerBinding = functionInfo.InputBindings.FirstOrDefault( - entry => DurableBindings.IsActivityTrigger(entry.Value.Type) - && entry.Value.Direction == BindingInfo.Types.Direction.In); - if (activityTriggerBinding.Key == null) - { - var message = string.Format(PowerShellWorkerStrings.FunctionDoesNotHaveProperActivityFunctionBinding, task.FunctionName); - throw new InvalidOperationException(message); - } - } } } diff --git a/src/Durable/Tasks/DurableTask.cs b/src/DurableSDK/Tasks/DurableTask.cs similarity index 89% rename from src/Durable/Tasks/DurableTask.cs rename to src/DurableSDK/Tasks/DurableTask.cs index af71f36d..228e769b 100644 --- a/src/Durable/Tasks/DurableTask.cs +++ b/src/DurableSDK/Tasks/DurableTask.cs @@ -11,9 +11,9 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks public abstract class DurableTask { - internal abstract HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context); + internal abstract HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context, bool processed = false); - internal abstract HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent scheduledHistoryEvent); + internal abstract HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent scheduledHistoryEvent, bool processed = false); internal abstract OrchestrationAction CreateOrchestrationAction(); } diff --git a/src/Durable/Tasks/DurableTimerTask.cs b/src/DurableSDK/Tasks/DurableTimerTask.cs similarity index 97% rename from src/Durable/Tasks/DurableTimerTask.cs rename to src/DurableSDK/Tasks/DurableTimerTask.cs index 86a75abe..4bb357ef 100644 --- a/src/Durable/Tasks/DurableTimerTask.cs +++ b/src/DurableSDK/Tasks/DurableTimerTask.cs @@ -28,7 +28,7 @@ internal DurableTimerTask( Action = new CreateDurableTimerAction(FireAt); } - internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context) + internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context, bool processed) { return context.History.FirstOrDefault( e => e.EventType == HistoryEventType.TimerCreated && @@ -36,7 +36,7 @@ internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext con !e.IsProcessed); } - internal override HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent scheduledHistoryEvent) + internal override HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent scheduledHistoryEvent, bool processed) { return scheduledHistoryEvent == null ? null diff --git a/src/Durable/Tasks/ExternalEventTask.cs b/src/DurableSDK/Tasks/ExternalEventTask.cs similarity index 88% rename from src/Durable/Tasks/ExternalEventTask.cs rename to src/DurableSDK/Tasks/ExternalEventTask.cs index 1bd1fc58..5d12ad66 100644 --- a/src/Durable/Tasks/ExternalEventTask.cs +++ b/src/DurableSDK/Tasks/ExternalEventTask.cs @@ -21,17 +21,17 @@ public ExternalEventTask(string externalEventName) } // There is no corresponding history event for an expected external event - internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context) + internal override HistoryEvent GetScheduledHistoryEvent(OrchestrationContext context, bool processed) { return null; } - internal override HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent taskScheduled) + internal override HistoryEvent GetCompletedHistoryEvent(OrchestrationContext context, HistoryEvent taskScheduled, bool processed) { return context.History.FirstOrDefault( e => e.EventType == HistoryEventType.EventRaised && e.Name == ExternalEventName && - !e.IsProcessed); + e.IsPlayed == processed); } internal override OrchestrationAction CreateOrchestrationAction() diff --git a/src/Durable/DurableBindings.cs b/src/DurableWorker/DurableBindings.cs similarity index 95% rename from src/Durable/DurableBindings.cs rename to src/DurableWorker/DurableBindings.cs index 6bdf2468..d46a4adc 100644 --- a/src/Durable/DurableBindings.cs +++ b/src/DurableWorker/DurableBindings.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { using System; diff --git a/src/Durable/DurableController.cs b/src/DurableWorker/DurableController.cs similarity index 64% rename from src/Durable/DurableController.cs rename to src/DurableWorker/DurableController.cs index 6b1f31bb..3d756e15 100644 --- a/src/Durable/DurableController.cs +++ b/src/DurableWorker/DurableController.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable { - using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,6 +15,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable using WebJobs.Script.Grpc.Messages; using PowerShellWorker.Utility; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; /// /// The main entry point for durable functions support. @@ -47,9 +47,14 @@ internal DurableController( _orchestrationInvoker = orchestrationInvoker; } - public void BeforeFunctionInvocation(IList inputData) + public string GetOrchestrationParameterName() { - // If the function is an orchestration client, then we set the DurableClient + return _orchestrationBindingInfo?.ParameterName; + } + + public void InitializeBindings(IList inputData) + { + // If the function is an durable client, then we set the DurableClient // in the module context for the 'Start-DurableOrchestration' function to use. if (_durableFunctionInfo.IsDurableClient) { @@ -58,11 +63,14 @@ public void BeforeFunctionInvocation(IList inputData) .Data.ToObject(); _powerShellServices.SetDurableClient(durableClient); + } else if (_durableFunctionInfo.IsOrchestrationFunction) { - _orchestrationBindingInfo = CreateOrchestrationBindingInfo(inputData); - _powerShellServices.SetOrchestrationContext(_orchestrationBindingInfo.Context); + _orchestrationBindingInfo = _powerShellServices.SetOrchestrationContext( + inputData[0], + out IExternalInvoker externalInvoker); + _orchestrationInvoker.SetExternalInvoker(externalInvoker); } } @@ -87,46 +95,22 @@ public bool TryGetInputBindingParameterValue(string bindingName, out object valu public void AddPipelineOutputIfNecessary(Collection pipelineItems, Hashtable result) { - var shouldAddPipelineOutput = - _durableFunctionInfo.Type == DurableFunctionType.ActivityFunction; - - if (shouldAddPipelineOutput) + + if (ShouldSuppressPipelineTraces()) { var returnValue = FunctionReturnValueBuilder.CreateReturnValueFromFunctionOutput(pipelineItems); result.Add(AzFunctionInfo.DollarReturn, returnValue); } } - public bool TryInvokeOrchestrationFunction(out Hashtable result) + public Hashtable InvokeOrchestrationFunction() { - if (!_durableFunctionInfo.IsOrchestrationFunction) - { - result = null; - return false; - } - - result = _orchestrationInvoker.Invoke(_orchestrationBindingInfo, _powerShellServices); - return true; + return _orchestrationInvoker.Invoke(_orchestrationBindingInfo, _powerShellServices); } public bool ShouldSuppressPipelineTraces() { return _durableFunctionInfo.Type == DurableFunctionType.ActivityFunction; } - - private static OrchestrationBindingInfo CreateOrchestrationBindingInfo(IList inputData) - { - // Quote from https://docs.microsoft.com/en-us/azure/azure-functions/durable-functions-bindings: - // - // "Orchestrator functions should never use any input or output bindings other than the orchestration trigger binding. - // Doing so has the potential to cause problems with the Durable Task extension because those bindings may not obey the single-threading and I/O rules." - // - // Therefore, it's by design that input data contains only one item, which is the metadata of the orchestration context. - var context = inputData[0]; - - return new OrchestrationBindingInfo( - context.Name, - JsonConvert.DeserializeObject(context.Data.String)); - } } } diff --git a/src/Durable/DurableFunctionInfo.cs b/src/DurableWorker/DurableFunctionInfo.cs similarity index 84% rename from src/Durable/DurableFunctionInfo.cs rename to src/DurableWorker/DurableFunctionInfo.cs index a245ac67..05c3650d 100644 --- a/src/Durable/DurableFunctionInfo.cs +++ b/src/DurableWorker/DurableFunctionInfo.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { internal class DurableFunctionInfo { @@ -13,9 +13,12 @@ public DurableFunctionInfo(DurableFunctionType type, string durableClientBinding DurableClientBindingName = durableClientBindingName; } + public bool IsActivityFunction => Type == DurableFunctionType.ActivityFunction; + public bool IsDurableClient => DurableClientBindingName != null; public bool IsOrchestrationFunction => Type == DurableFunctionType.OrchestrationFunction; + public string DurableClientBindingName { get; } diff --git a/src/Durable/DurableFunctionInfoFactory.cs b/src/DurableWorker/DurableFunctionInfoFactory.cs similarity index 96% rename from src/Durable/DurableFunctionInfoFactory.cs rename to src/DurableWorker/DurableFunctionInfoFactory.cs index 3e65b9f7..0cc08d27 100644 --- a/src/Durable/DurableFunctionInfoFactory.cs +++ b/src/DurableWorker/DurableFunctionInfoFactory.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { using System.Linq; diff --git a/src/Durable/DurableFunctionType.cs b/src/DurableWorker/DurableFunctionType.cs similarity index 80% rename from src/Durable/DurableFunctionType.cs rename to src/DurableWorker/DurableFunctionType.cs index 73feb504..2ecacc17 100644 --- a/src/Durable/DurableFunctionType.cs +++ b/src/DurableWorker/DurableFunctionType.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -namespace Microsoft.Azure.Functions.PowerShellWorker.Durable +namespace Microsoft.Azure.Functions.PowerShellWorker.DurableWorker { internal enum DurableFunctionType { diff --git a/src/FunctionInfo.cs b/src/FunctionInfo.cs index 2aea62ec..091eecde 100644 --- a/src/FunctionInfo.cs +++ b/src/FunctionInfo.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker { - using Durable; + using DurableWorker; /// /// This type represents the metadata of an Azure PowerShell Function. diff --git a/src/Messaging/MessagingStream.cs b/src/Messaging/MessagingStream.cs index 82f1e108..596c7737 100644 --- a/src/Messaging/MessagingStream.cs +++ b/src/Messaging/MessagingStream.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Grpc.Core; +using Grpc.Net.Client; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; namespace Microsoft.Azure.Functions.PowerShellWorker.Messaging @@ -19,15 +20,24 @@ internal class MessagingStream internal MessagingStream(string host, int port) { + // To call unsecured gRPC services, ensure the address starts with 'http' as opposed to 'https'. + // For more detail, see https://docs.microsoft.com/en-us/aspnet/core/grpc/client?view=aspnetcore-6.0 + string uriString = $"http://{host}:{port}"; + if (!Uri.TryCreate(uriString, UriKind.Absolute, out Uri grpcUri)) + { + throw new InvalidOperationException($"The gRPC channel URI '{uriString}' could not be parsed."); + } + const int maxMessageLength = int.MaxValue; - var channelOptions = new [] + var channelOptions = new GrpcChannelOptions { - new ChannelOption(ChannelOptions.MaxReceiveMessageLength, maxMessageLength), - new ChannelOption(ChannelOptions.MaxSendMessageLength, maxMessageLength) + MaxReceiveMessageSize = maxMessageLength, + MaxSendMessageSize = maxMessageLength, + Credentials = ChannelCredentials.Insecure }; - Channel channel = new Channel(host, port, ChannelCredentials.Insecure, channelOptions); + GrpcChannel channel = GrpcChannel.ForAddress(grpcUri, channelOptions); _call = new FunctionRpc.FunctionRpcClient(channel).EventStream(); } diff --git a/src/Microsoft.Azure.Functions.PowerShellWorker.csproj b/src/Microsoft.Azure.Functions.PowerShellWorker.csproj index f245fd06..31099354 100644 --- a/src/Microsoft.Azure.Functions.PowerShellWorker.csproj +++ b/src/Microsoft.Azure.Functions.PowerShellWorker.csproj @@ -20,10 +20,10 @@ Licensed under the MIT license. See LICENSE file in the project root for full li - + - + diff --git a/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psd1 b/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psd1 index 7ae51c2b..7bed18b9 100644 --- a/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psd1 +++ b/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psd1 @@ -59,6 +59,7 @@ FunctionsToExport = @( # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @( 'Get-OutputBinding', + 'Get-DurableTaskResult' 'Invoke-DurableActivity', 'Push-OutputBinding', 'Set-DurableCustomStatus', diff --git a/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psm1 b/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psm1 index 1d4d00ad..d0a285e0 100644 --- a/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psm1 +++ b/src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psm1 @@ -11,7 +11,7 @@ Set-Alias -Name Start-NewOrchestration -Value Start-DurableOrchestration function GetDurableClientFromModulePrivateData { $PrivateData = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData - if ($PrivateData -eq $null -or $PrivateData['DurableClient'] -eq $null) { + if ($null -eq $PrivateData -or $null -eq $PrivateData['DurableClient']) { throw "No binding of the type 'durableClient' was defined." } else { @@ -98,7 +98,11 @@ function Start-DurableOrchestration { [Parameter( ValueFromPipelineByPropertyName=$true)] - [object] $DurableClient + [object] $DurableClient, + + [Parameter( + ValueFromPipelineByPropertyName=$true)] + [string] $InstanceId ) $ErrorActionPreference = 'Stop' @@ -107,7 +111,10 @@ function Start-DurableOrchestration { $DurableClient = GetDurableClientFromModulePrivateData } - $InstanceId = (New-Guid).Guid + # TODO: Port this change to the External SDK + if (-not $InstanceId) { + $InstanceId = (New-Guid).Guid + } $Uri = if ($DurableClient.rpcBaseUrl) { @@ -235,6 +242,8 @@ function New-DurableOrchestrationCheckStatusResponse { The TaskHubName of the orchestration instance that will handle the external event. .PARAMETER ConnectionName The name of the connection string associated with TaskHubName +.PARAMETER AppCode + The Azure Functions system key #> function Send-DurableExternalEvent { [CmdletBinding()] @@ -263,12 +272,16 @@ function Send-DurableExternalEvent { [Parameter( ValueFromPipelineByPropertyName=$true)] - [string] $ConnectionName + [string] $ConnectionName, + + [Parameter( + ValueFromPipelineByPropertyName=$true)] + [string] $AppCode ) $DurableClient = GetDurableClientFromModulePrivateData - $RequestUrl = GetRaiseEventUrl -DurableClient $DurableClient -InstanceId $InstanceId -EventName $EventName -TaskHubName $TaskHubName -ConnectionName $ConnectionName + $RequestUrl = GetRaiseEventUrl -DurableClient $DurableClient -InstanceId $InstanceId -EventName $EventName -TaskHubName $TaskHubName -ConnectionName $ConnectionName -AppCode $AppCode $Body = $EventData | ConvertTo-Json -Compress @@ -280,7 +293,8 @@ function GetRaiseEventUrl( [string] $InstanceId, [string] $EventName, [string] $TaskHubName, - [string] $ConnectionName) { + [string] $ConnectionName, + [string] $AppCode) { $RequestUrl = $DurableClient.BaseUrl + "/instances/$InstanceId/raiseEvent/$EventName" @@ -291,6 +305,9 @@ function GetRaiseEventUrl( if ($null -eq $ConnectionName) { $query += "connection=$ConnectionName" } + if ($null -eq $AppCode) { + $query += "code=$AppCode" + } if ($query.Count -gt 0) { $RequestUrl += "?" + [string]::Join("&", $query) } diff --git a/src/PowerShell/PowerShellManager.cs b/src/PowerShell/PowerShellManager.cs index 7e458914..0344c05d 100644 --- a/src/PowerShell/PowerShellManager.cs +++ b/src/PowerShell/PowerShellManager.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell { + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using System.Management.Automation; using System.Text; @@ -204,32 +205,37 @@ public Hashtable InvokeFunction( FunctionInvocationPerformanceStopwatch stopwatch) { var outputBindings = FunctionMetadata.GetOutputBindingHashtable(_pwsh.Runspace.InstanceId); - - var durableController = new DurableController(functionInfo.DurableFunctionInfo, _pwsh); + var durableFunctionsUtils = new DurableController(functionInfo.DurableFunctionInfo, _pwsh); try { - durableController.BeforeFunctionInvocation(inputData); + durableFunctionsUtils.InitializeBindings(inputData); AddEntryPointInvocationCommand(functionInfo); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.FunctionCodeReady); - SetInputBindingParameterValues(functionInfo, inputData, durableController, triggerMetadata, traceContext, retryContext); + var orchestrationParamName = durableFunctionsUtils.GetOrchestrationParameterName(); + SetInputBindingParameterValues(functionInfo, inputData, orchestrationParamName, triggerMetadata, traceContext, retryContext); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.InputBindingValuesReady); - if (!durableController.ShouldSuppressPipelineTraces()) - { - _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject"); - } - stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.InvokingFunctionCode); Logger.Log(isUserOnlyLog: false, LogLevel.Trace, CreateInvocationPerformanceReportMessage(functionInfo.FuncName, stopwatch)); try { - return durableController.TryInvokeOrchestrationFunction(out var result) - ? result - : InvokeNonOrchestrationFunction(durableController, outputBindings); + if(functionInfo.DurableFunctionInfo.IsOrchestrationFunction) + { + return durableFunctionsUtils.InvokeOrchestrationFunction(); + } + else + { + var isActivityFunction = functionInfo.DurableFunctionInfo.IsActivityFunction; + if (!isActivityFunction) + { + _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject"); + } + return ExecuteUserCode(isActivityFunction, outputBindings); + } } catch (RuntimeException e) { @@ -237,9 +243,9 @@ public Hashtable InvokeFunction( Logger.Log(isUserOnlyLog: true, LogLevel.Error, GetFunctionExceptionMessage(e)); throw; } - catch (OrchestrationFailureException e) + catch (Exception e) { - if (e.InnerException is IContainsErrorRecord inner) + if (e.Data.Contains(OrchestrationInvoker.isOrchestrationFailureKey) && e.InnerException is IContainsErrorRecord inner) { Logger.Log(isUserOnlyLog: true, LogLevel.Error, GetFunctionExceptionMessage(inner)); } @@ -248,7 +254,7 @@ public Hashtable InvokeFunction( } finally { - durableController.AfterFunctionInvocation(); + durableFunctionsUtils.AfterFunctionInvocation(); outputBindings.Clear(); ResetRunspace(); } @@ -257,7 +263,7 @@ public Hashtable InvokeFunction( private void SetInputBindingParameterValues( AzFunctionInfo functionInfo, IEnumerable inputData, - DurableController durableController, + string orchParamName, Hashtable triggerMetadata, TraceContext traceContext, RetryContext retryContext) @@ -266,13 +272,12 @@ private void SetInputBindingParameterValues( { if (functionInfo.FuncParameters.TryGetValue(binding.Name, out var paramInfo)) { - if (!durableController.TryGetInputBindingParameterValue(binding.Name, out var valueToUse)) + if (string.CompareOrdinal(binding.Name, orchParamName) != 0) { var bindingInfo = functionInfo.InputBindings[binding.Name]; - valueToUse = Utils.TransformInBindingValueAsNeeded(paramInfo, bindingInfo, binding.Data.ToObject()); + var valueToUse = Utils.TransformInBindingValueAsNeeded(paramInfo, bindingInfo, binding.Data.ToObject()); + _pwsh.AddParameter(binding.Name, valueToUse); } - - _pwsh.AddParameter(binding.Name, valueToUse); } } @@ -296,11 +301,15 @@ private void SetInputBindingParameterValues( /// /// Execution a function fired by a trigger or an activity function scheduled by an orchestration. /// - private Hashtable InvokeNonOrchestrationFunction(DurableController durableController, IDictionary outputBindings) + private Hashtable ExecuteUserCode(bool addPipelineOutput, IDictionary outputBindings) { var pipelineItems = _pwsh.InvokeAndClearCommands(); var result = new Hashtable(outputBindings, StringComparer.OrdinalIgnoreCase); - durableController.AddPipelineOutputIfNecessary(pipelineItems, result); + if (addPipelineOutput) + { + var returnValue = FunctionReturnValueBuilder.CreateReturnValueFromFunctionOutput(pipelineItems); + result.Add(AzFunctionInfo.DollarReturn, returnValue); + } return result; } diff --git a/src/PowerShell/PowerShellManagerPool.cs b/src/PowerShell/PowerShellManagerPool.cs index 43676cd5..94ad8766 100644 --- a/src/PowerShell/PowerShellManagerPool.cs +++ b/src/PowerShell/PowerShellManagerPool.cs @@ -82,7 +82,7 @@ internal PowerShellManager CheckoutIdleWorker( if (psManager == null) { var logger = CreateLoggerWithContext(requestId, invocationId); - logger.Log(isUserOnlyLog: true, LogLevel.Warning, string.Format(PowerShellWorkerStrings.FunctionQueuingRequest, functionName)); + logger.Log(isUserOnlyLog: false, LogLevel.Warning, string.Format(PowerShellWorkerStrings.FunctionQueuingRequest, functionName)); // If the pool has reached its bounded capacity, then the thread // should be blocked until an idle one becomes available. diff --git a/src/RequestProcessor.cs b/src/RequestProcessor.cs index 6aabd3f3..93a1271f 100644 --- a/src/RequestProcessor.cs +++ b/src/RequestProcessor.cs @@ -13,7 +13,7 @@ using Microsoft.Azure.Functions.PowerShellWorker.PowerShell; using Microsoft.Azure.Functions.PowerShellWorker.Utility; using Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement; -using Microsoft.Azure.Functions.PowerShellWorker.Durable; +using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; namespace Microsoft.Azure.Functions.PowerShellWorker diff --git a/src/Utility/Utils.cs b/src/Utility/Utils.cs index 549ba5de..21ec68b8 100644 --- a/src/Utility/Utils.cs +++ b/src/Utility/Utils.cs @@ -17,6 +17,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Utility internal class Utils { + internal readonly static CmdletInfo GetModuleCmdletInfo = new CmdletInfo("Get-Module", typeof(GetModuleCommand)); internal readonly static CmdletInfo ImportModuleCmdletInfo = new CmdletInfo("Import-Module", typeof(ImportModuleCommand)); internal readonly static CmdletInfo RemoveModuleCmdletInfo = new CmdletInfo("Remove-Module", typeof(RemoveModuleCommand)); internal readonly static CmdletInfo RemoveJobCmdletInfo = new CmdletInfo("Remove-Job", typeof(RemoveJobCommand)); diff --git a/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/DurableEndToEndTests.cs b/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/DurableEndToEndTests.cs index ddc20c5e..067e9fba 100644 --- a/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/DurableEndToEndTests.cs +++ b/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/DurableEndToEndTests.cs @@ -147,6 +147,112 @@ public async Task LegacyDurableCommandNamesStillWork() } } + [Fact] + public async Task OrchestratationContextHasAllExpectedProperties() + { + var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClientOrchContextProperties", queryString: string.Empty); + Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode); + + var initialResponseBody = await initialResponse.Content.ReadAsStringAsync(); + dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody); + var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri; + + var startTime = DateTime.UtcNow; + + using (var httpClient = new HttpClient()) + { + while (true) + { + var statusResponse = await httpClient.GetAsync(statusQueryGetUri); + switch (statusResponse.StatusCode) + { + case HttpStatusCode.Accepted: + { + var statusResponseBody = await GetResponseBodyAsync(statusResponse); + var runtimeStatus = (string)statusResponseBody.runtimeStatus; + Assert.True( + runtimeStatus == "Running" || runtimeStatus == "Pending", + $"Unexpected runtime status: {runtimeStatus}"); + + if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout) + { + Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}"); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); + break; + } + + case HttpStatusCode.OK: + { + var statusResponseBody = await GetResponseBodyAsync(statusResponse); + Assert.Equal("Completed", (string)statusResponseBody.runtimeStatus); + Assert.Equal("True", statusResponseBody.output[0].ToString()); + Assert.Equal("Hello myInstanceId", statusResponseBody.output[1].ToString()); + Assert.Equal("False", statusResponseBody.output[2].ToString()); + return; + } + + default: + Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}"); + break; + } + } + } + } + + [Fact] + public async Task OrchestratationCanAlwaysObtainTaskResult() + { + var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClient", queryString: "?FunctionName=DurableOrchestratorGetTaskResult"); + Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode); + + var initialResponseBody = await initialResponse.Content.ReadAsStringAsync(); + dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody); + var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri; + + var startTime = DateTime.UtcNow; + + using (var httpClient = new HttpClient()) + { + while (true) + { + var statusResponse = await httpClient.GetAsync(statusQueryGetUri); + switch (statusResponse.StatusCode) + { + case HttpStatusCode.Accepted: + { + var statusResponseBody = await GetResponseBodyAsync(statusResponse); + var runtimeStatus = (string)statusResponseBody.runtimeStatus; + Assert.True( + runtimeStatus == "Running" || runtimeStatus == "Pending", + $"Unexpected runtime status: {runtimeStatus}"); + + if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout) + { + Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}"); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); + break; + } + + case HttpStatusCode.OK: + { + var statusResponseBody = await GetResponseBodyAsync(statusResponse); + Assert.Equal("Completed", (string)statusResponseBody.runtimeStatus); + Assert.Equal("Hello world", statusResponseBody.output.ToString()); + return; + } + + default: + Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}"); + break; + } + } + } + } + [Fact] public async Task ActivityCanHaveQueueBinding() { diff --git a/test/E2E/TestFunctionApp/DurableClientOrchContextProperties/function.json b/test/E2E/TestFunctionApp/DurableClientOrchContextProperties/function.json new file mode 100644 index 00000000..ce618d34 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableClientOrchContextProperties/function.json @@ -0,0 +1,24 @@ +{ + "bindings": [ + { + "authLevel": "function", + "name": "Request", + "type": "httpTrigger", + "direction": "in", + "methods": [ + "post", + "get" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + }, + { + "name": "starter", + "type": "durableClient", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/test/E2E/TestFunctionApp/DurableClientOrchContextProperties/run.ps1 b/test/E2E/TestFunctionApp/DurableClientOrchContextProperties/run.ps1 new file mode 100644 index 00000000..228d57f6 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableClientOrchContextProperties/run.ps1 @@ -0,0 +1,9 @@ +using namespace System.Net + +param($Request, $TriggerMetadata) + +$InstanceId = Start-DurableOrchestration -FunctionName "DurableOrchestratorAccessContextProps" -InstanceId "myInstanceId" +Write-Host "Started orchestration with ID = '$InstanceId'" + +$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId +Push-OutputBinding -Name Response -Value $Response diff --git a/test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/function.json b/test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/function.json new file mode 100644 index 00000000..336f5a18 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "Context", + "type": "orchestrationTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/run.ps1 b/test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/run.ps1 new file mode 100644 index 00000000..031a5492 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableOrchestratorAccessContextProps/run.ps1 @@ -0,0 +1,8 @@ +param($Context) + +$output = @() + +$output += $Context.IsReplaying +$output += Invoke-DurableActivity -FunctionName 'DurableActivity' -Input $Context.InstanceId +$output += $Context.IsReplaying +$output diff --git a/test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/function.json b/test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/function.json new file mode 100644 index 00000000..336f5a18 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "Context", + "type": "orchestrationTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/run.ps1 b/test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/run.ps1 new file mode 100644 index 00000000..23380916 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableOrchestratorGetTaskResult/run.ps1 @@ -0,0 +1,8 @@ +param($Context) + +$output = @() + +$task = Invoke-DurableActivity -FunctionName 'DurableActivity' -Input "world" -NoWait +$firstTask = Wait-DurableTask -Task @($task) -Any +$output += Get-DurableTaskResult -Task @($firstTask) +$output diff --git a/test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/function.json b/test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/function.json new file mode 100644 index 00000000..336f5a18 --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "Context", + "type": "orchestrationTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/run.ps1 b/test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/run.ps1 new file mode 100644 index 00000000..d4a75a7f --- /dev/null +++ b/test/E2E/TestFunctionApp/DurableOrchestratorRaiseEvent/run.ps1 @@ -0,0 +1,9 @@ +param($Context) + +$output = @() + +$output += Invoke-DurableActivity -FunctionName 'Hello' -Input 'Tokyo' +$output += Invoke-DurableActivity -FunctionName 'Hello' -Input 'Seattle' +$output += Invoke-DurableActivity -FunctionName 'Hello' -Input 'London' + +$output diff --git a/test/E2E/TestFunctionApp/profile.ps1 b/test/E2E/TestFunctionApp/profile.ps1 new file mode 100644 index 00000000..9afdf1da --- /dev/null +++ b/test/E2E/TestFunctionApp/profile.ps1 @@ -0,0 +1,22 @@ +# Azure Functions profile.ps1 +# +# This profile.ps1 will get executed every "cold start" of your Function App. +# "cold start" occurs when: +# +# * A Function App starts up for the very first time +# * A Function App starts up after being de-allocated due to inactivity +# +# You can define helper functions, run commands, or specify environment variables +# NOTE: any variables defined that are not environment variables will get reset after the first execution + +# Authenticate with Azure PowerShell using MSI. +# Remove this if you are not planning on using MSI or Azure PowerShell. +if ($env:MSI_SECRET) { + Disable-AzContextAutosave -Scope Process | Out-Null + Connect-AzAccount -Identity +} + +# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. +# Enable-AzureRmAlias + +# You can also define functions or aliases that can be referenced in any of your PowerShell functions. \ No newline at end of file diff --git a/test/Unit/Durable/ActivityInvocationTaskTests.cs b/test/Unit/Durable/ActivityInvocationTaskTests.cs index c96cc755..96e27571 100644 --- a/test/Unit/Durable/ActivityInvocationTaskTests.cs +++ b/test/Unit/Durable/ActivityInvocationTaskTests.cs @@ -163,59 +163,6 @@ public void StopAndInitiateDurableTaskOrReplay_OutputsActivityInvocationTask_Whe Assert.Equal(FunctionName, allOutput.Single().FunctionName); } - [Fact] - public void ValidateTask_Throws_WhenActivityFunctionDoesNotExist() - { - var history = CreateHistory(scheduled: false, completed: false, failed: false, output: InvocationResultJson); - var orchestrationContext = new OrchestrationContext { History = history }; - - var loadedFunctions = new[] - { - DurableTestUtilities.CreateFakeAzFunctionInfo(FunctionName, "fakeTriggerBindingName", ActivityTriggerBindingType, BindingInfo.Types.Direction.In) - }; - - const string wrongFunctionName = "AnotherFunction"; - - var durableTaskHandler = new DurableTaskHandler(); - - var exception = - Assert.Throws( - () => ActivityInvocationTask.ValidateTask( - new ActivityInvocationTask(wrongFunctionName, FunctionInput), loadedFunctions)); - - Assert.Contains(wrongFunctionName, exception.Message); - Assert.DoesNotContain(ActivityTriggerBindingType, exception.Message); - - DurableTestUtilities.VerifyNoActionAdded(orchestrationContext); - } - - [Theory] - [InlineData("IncorrectBindingType", BindingInfo.Types.Direction.In)] - [InlineData(ActivityTriggerBindingType, BindingInfo.Types.Direction.Out)] - public void ValidateTask_Throws_WhenActivityFunctionHasNoProperBinding( - string bindingType, BindingInfo.Types.Direction bindingDirection) - { - var history = CreateHistory(scheduled: false, completed: false, failed: false, output: InvocationResultJson); - var orchestrationContext = new OrchestrationContext { History = history }; - - var loadedFunctions = new[] - { - DurableTestUtilities.CreateFakeAzFunctionInfo(FunctionName, "fakeTriggerBindingName", bindingType, bindingDirection) - }; - - var durableTaskHandler = new DurableTaskHandler(); - - var exception = - Assert.Throws( - () => ActivityInvocationTask.ValidateTask( - new ActivityInvocationTask(FunctionName, FunctionInput), loadedFunctions)); - - Assert.Contains(FunctionName, exception.Message); - Assert.Contains(ActivityTriggerBindingType, exception.Message); - - DurableTestUtilities.VerifyNoActionAdded(orchestrationContext); - } - [Theory] [InlineData(false)] [InlineData(true)] @@ -225,8 +172,8 @@ public void GetCompletedHistoryEvent_ReturnsTaskCompletedOrTaskFailed(bool succe var orchestrationContext = new OrchestrationContext { History = history }; var task = new ActivityInvocationTask(FunctionName, FunctionInput); - var scheduledEvent = task.GetScheduledHistoryEvent(orchestrationContext); - var completedEvent = task.GetCompletedHistoryEvent(orchestrationContext, scheduledEvent); + var scheduledEvent = task.GetScheduledHistoryEvent(orchestrationContext, false); + var completedEvent = task.GetCompletedHistoryEvent(orchestrationContext, scheduledEvent, false); Assert.Equal(scheduledEvent.EventId, completedEvent.TaskScheduledId); } diff --git a/test/Unit/Durable/DurableControllerTests.cs b/test/Unit/Durable/DurableControllerTests.cs index 68531f7c..b57cb767 100644 --- a/test/Unit/Durable/DurableControllerTests.cs +++ b/test/Unit/Durable/DurableControllerTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.Durable using System.Collections.ObjectModel; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using Microsoft.Azure.Functions.PowerShellWorker.Durable; using Microsoft.Azure.Functions.PowerShellWorker.Utility; using Newtonsoft.Json; @@ -22,9 +23,12 @@ public class DurableControllerTests { private readonly Mock _mockPowerShellServices = new Mock(MockBehavior.Strict); private readonly Mock _mockOrchestrationInvoker = new Mock(MockBehavior.Strict); + private const string _contextParameterName = "ParameterName"; + private static readonly OrchestrationContext _orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; + private static readonly OrchestrationBindingInfo _orchestrationBindingInfo = new OrchestrationBindingInfo(_contextParameterName, _orchestrationContext); [Fact] - public void BeforeFunctionInvocation_SetsDurableClient_ForDurableClientFunction() + public void InitializeBindings_SetsDurableClient_ForDurableClientFunction() { var durableController = CreateDurableController(DurableFunctionType.None, "DurableClientBindingName"); @@ -38,7 +42,7 @@ public void BeforeFunctionInvocation_SetsDurableClient_ForDurableClientFunction( _mockPowerShellServices.Setup(_ => _.SetDurableClient(It.IsAny())); - durableController.BeforeFunctionInvocation(inputData); + durableController.InitializeBindings(inputData); _mockPowerShellServices.Verify( _ => _.SetDurableClient( @@ -47,50 +51,50 @@ public void BeforeFunctionInvocation_SetsDurableClient_ForDurableClientFunction( } [Fact] - public void BeforeFunctionInvocation_SetsOrchestrationContext_ForOrchestrationFunction() + public void InitializeBindings_SetsOrchestrationContext_ForOrchestrationFunction() { var durableController = CreateDurableController(DurableFunctionType.OrchestrationFunction); - - var orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; var inputData = new[] { - CreateParameterBinding("ParameterName", orchestrationContext) + CreateParameterBinding("ParameterName", _orchestrationContext) }; + + _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext(It.IsAny(), + out It.Ref.IsAny)) + .Returns(_orchestrationBindingInfo); + _mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny())); - _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext(It.IsAny())); - - durableController.BeforeFunctionInvocation(inputData); + durableController.InitializeBindings(inputData); _mockPowerShellServices.Verify( _ => _.SetOrchestrationContext( - It.Is(c => c.InstanceId == orchestrationContext.InstanceId)), + It.Is(c => c.Data.ToString().Contains(_orchestrationContext.InstanceId)), + out It.Ref.IsAny), Times.Once); } [Fact] - public void BeforeFunctionInvocation_Throws_OnOrchestrationFunctionWithoutContextParameter() + public void InitializeBindings_Throws_OnOrchestrationFunctionWithoutContextParameter() { var durableController = CreateDurableController(DurableFunctionType.OrchestrationFunction); var inputData = new ParameterBinding[0]; - Assert.ThrowsAny(() => durableController.BeforeFunctionInvocation(inputData)); + Assert.ThrowsAny(() => durableController.InitializeBindings(inputData)); } [Theory] [InlineData(DurableFunctionType.None)] [InlineData(DurableFunctionType.ActivityFunction)] - internal void BeforeFunctionInvocation_DoesNothing_ForNonOrchestrationFunction(DurableFunctionType durableFunctionType) + internal void InitializeBindings_DoesNothing_ForNonOrchestrationFunction(DurableFunctionType durableFunctionType) { var durableController = CreateDurableController(durableFunctionType); - var orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; - var inputData = new[] { // Even if a parameter similar to orchestration context is passed: - CreateParameterBinding("ParameterName", orchestrationContext) + CreateParameterBinding("ParameterName", _orchestrationContext) }; - durableController.BeforeFunctionInvocation(inputData); + durableController.InitializeBindings(inputData); } [Theory] @@ -111,19 +115,19 @@ internal void AfterFunctionInvocation_ClearsOrchestrationContext(DurableFunction public void TryGetInputBindingParameterValue_RetrievesOrchestrationContextParameter_ForOrchestrationFunction() { var durableController = CreateDurableController(DurableFunctionType.OrchestrationFunction); - - var orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; - const string contextParameterName = "ParameterName"; var inputData = new[] { - CreateParameterBinding(contextParameterName, orchestrationContext) + CreateParameterBinding(_contextParameterName, _orchestrationContext) }; - - _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext(It.IsAny())); - durableController.BeforeFunctionInvocation(inputData); - - Assert.True(durableController.TryGetInputBindingParameterValue(contextParameterName, out var value)); - Assert.Equal(orchestrationContext.InstanceId, ((OrchestrationContext)value).InstanceId); + _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext( + It.IsAny(), + out It.Ref.IsAny)) + .Returns(_orchestrationBindingInfo); + _mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny())); + durableController.InitializeBindings(inputData); + + Assert.True(durableController.TryGetInputBindingParameterValue(_contextParameterName, out var value)); + Assert.Equal(_orchestrationContext.InstanceId, ((OrchestrationContext)value).InstanceId); } [Theory] @@ -132,70 +136,52 @@ public void TryGetInputBindingParameterValue_RetrievesOrchestrationContextParame internal void TryGetInputBindingParameterValue_RetrievesNothing_ForNonOrchestrationFunction(DurableFunctionType durableFunctionType) { var durableController = CreateDurableController(durableFunctionType); - - var orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; - const string contextParameterName = "ParameterName"; var inputData = new[] { - CreateParameterBinding(contextParameterName, orchestrationContext) + CreateParameterBinding(_contextParameterName, _orchestrationContext) }; - _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext(It.IsAny())); - durableController.BeforeFunctionInvocation(inputData); + _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext( + It.IsAny(), + out It.Ref.IsAny)) + .Returns(_orchestrationBindingInfo); + _mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny())); + durableController.InitializeBindings(inputData); - Assert.False(durableController.TryGetInputBindingParameterValue(contextParameterName, out var value)); + Assert.False(durableController.TryGetInputBindingParameterValue(_contextParameterName, out var value)); Assert.Null(value); } [Fact] public void TryInvokeOrchestrationFunction_InvokesOrchestrationFunction() { - var contextParameterName = "ParameterName"; - var orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; - var inputData = new[] { CreateParameterBinding(contextParameterName, orchestrationContext) }; - + var inputData = new[] { CreateParameterBinding(_contextParameterName, _orchestrationContext) }; var durableController = CreateDurableController(DurableFunctionType.OrchestrationFunction); - _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext(It.IsAny())); - durableController.BeforeFunctionInvocation(inputData); + _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext( + It.IsAny(), + out It.Ref.IsAny)) + .Returns(_orchestrationBindingInfo); + _mockOrchestrationInvoker.Setup(_ => _.SetExternalInvoker(It.IsAny())); + durableController.InitializeBindings(inputData); var expectedResult = new Hashtable(); _mockOrchestrationInvoker.Setup( _ => _.Invoke(It.IsAny(), It.IsAny())) .Returns(expectedResult); - var invoked = durableController.TryInvokeOrchestrationFunction(out var actualResult); - Assert.True(invoked); + var actualResult = durableController.InvokeOrchestrationFunction(); Assert.Same(expectedResult, actualResult); _mockOrchestrationInvoker.Verify( _ => _.Invoke( It.Is( - bindingInfo => bindingInfo.Context.InstanceId == orchestrationContext.InstanceId - && bindingInfo.ParameterName == contextParameterName), + bindingInfo => bindingInfo.Context.InstanceId == _orchestrationContext.InstanceId + && bindingInfo.ParameterName == _contextParameterName), _mockPowerShellServices.Object), Times.Once); } - [Theory] - [InlineData(DurableFunctionType.None)] - [InlineData(DurableFunctionType.ActivityFunction)] - internal void TryInvokeOrchestrationFunction_DoesNotInvokeNonOrchestrationFunction(DurableFunctionType durableFunctionType) - { - var contextParameterName = "ParameterName"; - var orchestrationContext = new OrchestrationContext { InstanceId = Guid.NewGuid().ToString() }; - var inputData = new[] { CreateParameterBinding(contextParameterName, orchestrationContext) }; - - var durableController = CreateDurableController(durableFunctionType); - - _mockPowerShellServices.Setup(_ => _.SetOrchestrationContext(It.IsAny())); - durableController.BeforeFunctionInvocation(inputData); - - var invoked = durableController.TryInvokeOrchestrationFunction(out var actualResult); - Assert.False(invoked); - Assert.Null(actualResult); - } - [Fact] public void AddPipelineOutputIfNecessary_AddsDollarReturn_ForActivityFunction() { diff --git a/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs b/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs index 09cea266..2c898316 100644 --- a/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs +++ b/test/Unit/Durable/DurableFunctionInfoFactoryTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.Durable using Xunit; - using Microsoft.Azure.Functions.PowerShellWorker.Durable; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; public class DurableFunctionInfoFactoryTests { diff --git a/test/Unit/Durable/OrchestrationInvokerTests.cs b/test/Unit/Durable/OrchestrationInvokerTests.cs index 65c4b24d..2a2db694 100644 --- a/test/Unit/Durable/OrchestrationInvokerTests.cs +++ b/test/Unit/Durable/OrchestrationInvokerTests.cs @@ -34,12 +34,16 @@ public void InvocationRunsToCompletionIfNotStopped() { var invocationAsyncResult = DurableTestUtilities.CreateInvocationResult(completed: true); DurableTestUtilities.ExpectBeginInvoke(_mockPowerShellServices, invocationAsyncResult); + _mockPowerShellServices.Setup(_ => _.UseExternalDurableSDK()).Returns(false); _orchestrationInvoker.Invoke(_orchestrationBindingInfo, _mockPowerShellServices.Object); _mockPowerShellServices.Verify(_ => _.BeginInvoke(It.IsAny>()), Times.Once); _mockPowerShellServices.Verify(_ => _.EndInvoke(invocationAsyncResult), Times.Once); _mockPowerShellServices.Verify(_ => _.ClearStreamsAndCommands(), Times.Once); + _mockPowerShellServices.Verify(_ => _.TracePipelineObject(), Times.Once); + _mockPowerShellServices.Verify(_ => _.AddParameter(It.IsAny(), It.IsAny()), Times.Once); + _mockPowerShellServices.Verify(_ => _.UseExternalDurableSDK(), Times.Once); _mockPowerShellServices.VerifyNoOtherCalls(); } @@ -47,10 +51,14 @@ public void InvocationRunsToCompletionIfNotStopped() public void InvocationStopsOnStopEvent() { InvokeOrchestration(completed: false); + _mockPowerShellServices.Setup(_ => _.UseExternalDurableSDK()).Returns(false); _mockPowerShellServices.Verify(_ => _.BeginInvoke(It.IsAny>()), Times.Once); _mockPowerShellServices.Verify(_ => _.StopInvoke(), Times.Once); _mockPowerShellServices.Verify(_ => _.ClearStreamsAndCommands(), Times.Once); + _mockPowerShellServices.Verify(_ => _.TracePipelineObject(), Times.Once); + _mockPowerShellServices.Verify(_ => _.AddParameter(It.IsAny(), It.IsAny()), Times.Once); + _mockPowerShellServices.Verify(_ => _.UseExternalDurableSDK(), Times.Once); _mockPowerShellServices.VerifyNoOtherCalls(); } diff --git a/test/Unit/PowerShell/PowerShellManagerTests.cs b/test/Unit/PowerShell/PowerShellManagerTests.cs index 5d4faf42..36588611 100644 --- a/test/Unit/PowerShell/PowerShellManagerTests.cs +++ b/test/Unit/PowerShell/PowerShellManagerTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test using System.Collections.ObjectModel; using System.Management.Automation; using Microsoft.Azure.Functions.PowerShellWorker.Durable; + using Microsoft.Azure.Functions.PowerShellWorker.DurableWorker; using Newtonsoft.Json; internal class TestUtils diff --git a/tools/helper.psm1 b/tools/helper.psm1 index 1b07797a..b01bb542 100644 --- a/tools/helper.psm1 +++ b/tools/helper.psm1 @@ -22,20 +22,11 @@ $DotnetSDKVersionRequirements = @{ } } -$GrpcToolsVersion = '2.27.0' # grpc.tools -$GoogleProtobufToolsVersion = '3.11.4' # google.protobuf.tools - -function AddLocalDotnetDirPath { - $LocalDotnetDirPath = if ($IsWindowsEnv) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" } - if (($env:PATH -split [IO.Path]::PathSeparator) -notcontains $LocalDotnetDirPath) { - $env:PATH = $LocalDotnetDirPath + [IO.Path]::PathSeparator + $env:PATH - } -} +$GrpcToolsVersion = '2.43.0' # grpc.tools +$GoogleProtobufToolsVersion = '3.19.4' # google.protobuf.tools function Find-Dotnet { - AddLocalDotnetDirPath - $listSdksOutput = dotnet --list-sdks $installedDotnetSdks = $listSdksOutput | ForEach-Object { $_.Split(" ")[0] } Write-Log "Detected dotnet SDKs: $($installedDotnetSdks -join ', ')" @@ -65,7 +56,7 @@ function Install-Dotnet { return # Simply return if we find dotnet SDk with the correct version } catch { } - $obtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain" + $obtainUrl = "https://raw.githubusercontent.com/dotnet/install-scripts/main/src" try { $installScript = if ($IsWindowsEnv) { "dotnet-install.ps1" } else { "dotnet-install.sh" } @@ -75,13 +66,12 @@ function Install-Dotnet { $version = "$majorMinorVersion.$($DotnetSDKVersionRequirements[$majorMinorVersion].DefaultPatch)" Write-Log "Installing dotnet SDK version $version" -Warning if ($IsWindowsEnv) { - & .\$installScript -Channel $Channel -Version $Version + & .\$installScript -Channel $Channel -Version $Version -InstallDir "$env:ProgramFiles/dotnet" } else { - bash ./$installScript -c $Channel -v $Version + bash ./$installScript -c $Channel -v $Version --install-dir /usr/share/dotnet } } - AddLocalDotnetDirPath } finally { Remove-Item $installScript -Force -ErrorAction SilentlyContinue diff --git a/tools/protobuf.tools.csproj b/tools/protobuf.tools.csproj index e4afec56..ed482be2 100644 --- a/tools/protobuf.tools.csproj +++ b/tools/protobuf.tools.csproj @@ -11,8 +11,8 @@ Licensed under the MIT license. See LICENSE file in the project root for full li - - + +