From 501df873d225b51f7f6fff12cc7cf7b9b36f1329 Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Wed, 24 Nov 2021 13:44:55 -0800 Subject: [PATCH 1/7] Updated subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Tag: v1.5.1-protobuf. Commit: 2fc14be59bbd8bb9f3f6275247047675b83fb3ca --- protobuf/README.md | 7 +- protobuf/src/proto/FunctionRpc.proto | 112 +++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/protobuf/README.md b/protobuf/README.md index a307acfc..14c406e2 100644 --- a/protobuf/README.md +++ b/protobuf/README.md @@ -61,7 +61,7 @@ mkdir %MSGDIR% set OUTDIR=%MSGDIR%\DotNet mkdir %OUTDIR% -%GRPC_TOOLS_PATH%\protoc.exe %PROTO% --csharp_out %OUTDIR% --grpc_out=%OUTDIR% --plugin=protoc-gen-grpc=%GRPC_TOOLS_PATH%\grpc_csharp_plugin.exe --proto_path=%PROTO_PATH% --proto_path=%PROTOBUF_TOOLS% +%GRPC_TOOLS_PATH%\protoc.exe %PROTO% --csharp_out %OUTDIR% --grpc_out=%OUTDIR% --plugin=protoc-gen-grpc=%GRPC_TOOLS_PATH%\grpc_csharp_plugin.exe --proto_path=%PROTO_PATH% --proto_path=%PROTOBUF_TOOLS% ``` ## JavaScript In package.json, add to the build script the following commands to build .js files and to build .ts files. Use and install npm package `protobufjs`. @@ -81,7 +81,10 @@ In pom.xml add following under configuration for this plugin ${basedir}//azure-functions-language-worker-protobuf/src/proto ## Python ---TODO +``` +python -m pip install -e .[dev] -U +python setup.py build +``` ## Contributing diff --git a/protobuf/src/proto/FunctionRpc.proto b/protobuf/src/proto/FunctionRpc.proto index 67a871de..49face20 100644 --- a/protobuf/src/proto/FunctionRpc.proto +++ b/protobuf/src/proto/FunctionRpc.proto @@ -67,10 +67,18 @@ message StreamingMessage { // Worker logs a message back to the host RpcLog rpc_log = 2; - + FunctionEnvironmentReloadRequest function_environment_reload_request = 25; FunctionEnvironmentReloadResponse function_environment_reload_response = 26; + + // Ask the worker to close any open shared memory resources for a given invocation + CloseSharedMemoryResourcesRequest close_shared_memory_resources_request = 27; + CloseSharedMemoryResourcesResponse close_shared_memory_resources_response = 28; + + // Worker indexing message types + FunctionsMetadataRequest functions_metadata_request = 29; + FunctionMetadataResponses function_metadata_responses = 30; } } @@ -201,6 +209,17 @@ message FunctionEnvironmentReloadResponse { StatusResult result = 3; } +// Tell the out-of-proc worker to close any shared memory maps it allocated for given invocation +message CloseSharedMemoryResourcesRequest { + repeated string map_names = 1; +} + +// Response from the worker indicating which of the shared memory maps have been successfully closed and which have not been closed +// The key (string) is the map name and the value (bool) is true if it was closed, false if not +message CloseSharedMemoryResourcesResponse { + map close_map_results = 1; +} + // Host tells the worker to load a Function message FunctionLoadRequest { // unique function identifier (avoid name collisions, facilitate reload case) @@ -245,6 +264,30 @@ message RpcFunctionMetadata { // Is set to true for proxy bool is_proxy = 7; + + // Function indexing status + StatusResult status = 8; + + // Function language + string language = 9; + + // Raw binding info + repeated string raw_bindings = 10; +} + +// Host tells worker it is ready to receive metadata +message FunctionsMetadataRequest { + // base directory for function app + string function_app_directory = 1; +} + +// Worker sends function metadata back to host +message FunctionMetadataResponses { + // list of function indexing responses + repeated FunctionLoadRequest function_load_requests_results = 1; + + // status of overall metadata request + StatusResult result = 2; } // Host requests worker to invoke a Function @@ -263,6 +306,9 @@ message InvocationRequest { // Populates activityId, tracestate and tags from host RpcTraceContext trace_context = 5; + + // Current retry context + RetryContext retry_context = 6; } // Host sends ActivityId, traceStateString and Tags from host @@ -277,6 +323,18 @@ message RpcTraceContext { map attributes = 3; } +// Host sends retry context for a function invocation +message RetryContext { + // Current retry count + int32 retry_count = 1; + + // Max retry count + int32 max_retry_count = 2; + + // Exception that caused the retry + RpcException exception = 3; +} + // Host requests worker to cancel invocation message InvocationCancel { // Unique id for invocation @@ -318,6 +376,34 @@ message TypedData { } } +// Specify which type of data is contained in the shared memory region being read +enum RpcDataType { + unknown = 0; + string = 1; + json = 2; + bytes = 3; + stream = 4; + http = 5; + int = 6; + double = 7; + collection_bytes = 8; + collection_string = 9; + collection_double = 10; + collection_sint64 = 11; +} + +// Used to provide metadata about shared memory region to read data from +message RpcSharedMemory { + // Name of the shared memory map containing data + string name = 1; + // Offset in the shared memory map to start reading data from + int64 offset = 2; + // Number of bytes to read (starting from the offset) + int64 count = 3; + // Final type to which the read data (in bytes) is to be interpreted as + RpcDataType type = 4; +} + // Used to encapsulate collection string message CollectionString { repeated string string = 1; @@ -343,8 +429,13 @@ message ParameterBinding { // Name for the binding string name = 1; - // Data for the binding - TypedData data = 2; + oneof rpc_data { + // Data for the binding + TypedData data = 2; + + // Metadata about the shared memory region to read data from + RpcSharedMemory rpc_shared_memory = 3; + } } // Used to describe a given binding on load @@ -390,8 +481,9 @@ message RpcLog { // Category of the log. Defaults to User if not specified. enum RpcLogCategory { - User = 0; - System = 1; + User = 0; + System = 1; + CustomMetric = 2; } // Unique id for invocation (if exists) @@ -413,11 +505,14 @@ message RpcLog { // Exception (if exists) RpcException exception = 6; - // json serialized property bag, or could use a type scheme like map + // json serialized property bag string properties = 7; - // Category of the log. Either user(default) or system. + // Category of the log. Either user(default), system, or custom metric. RpcLogCategory log_category = 8; + + // strongly-typed (ish) property bag + map propertiesMap = 9; } // Encapsulates an Exception @@ -484,4 +579,7 @@ message RpcHttp { TypedData rawBody = 17; repeated RpcClaimsIdentity identities = 18; repeated RpcHttpCookie cookies = 19; + map nullable_headers = 20; + map nullable_params = 21; + map nullable_query = 22; } From 100a2e0942dfe924f8f8d96f7678ac2555a34f4b Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Tue, 30 Nov 2021 17:50:41 -0800 Subject: [PATCH 2/7] Add RetryContext --- src/FunctionInfo.cs | 3 ++ src/PowerShell/PowerShellManager.cs | 11 +++++-- src/Public/RetryContext.cs | 29 +++++++++++++++++++ src/RequestProcessor.cs | 16 +++++++++- .../RetryContextEndToEndTests.cs | 27 +++++++++++++++++ .../Unit/PowerShell/PowerShellManagerTests.cs | 4 +-- 6 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/Public/RetryContext.cs create mode 100644 test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs diff --git a/src/FunctionInfo.cs b/src/FunctionInfo.cs index af8fbee8..2aea62ec 100644 --- a/src/FunctionInfo.cs +++ b/src/FunctionInfo.cs @@ -24,10 +24,12 @@ internal class AzFunctionInfo { internal const string TriggerMetadata = "TriggerMetadata"; internal const string TraceContext = "TraceContext"; + internal const string RetryContext = "RetryContext"; internal const string DollarReturn = "$return"; internal readonly bool HasTriggerMetadataParam; internal readonly bool HasTraceContextParam; + internal readonly bool HasRetryContextParam; internal readonly string FuncDirectory; internal readonly string FuncName; @@ -76,6 +78,7 @@ internal AzFunctionInfo(RpcFunctionMetadata metadata) var parametersCopy = new Dictionary(psScriptParams, StringComparer.OrdinalIgnoreCase); HasTriggerMetadataParam = parametersCopy.Remove(TriggerMetadata); HasTraceContextParam = parametersCopy.Remove(TraceContext); + HasRetryContextParam = parametersCopy.Remove(RetryContext); var allBindings = new Dictionary(StringComparer.OrdinalIgnoreCase); var inputBindings = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/PowerShell/PowerShellManager.cs b/src/PowerShell/PowerShellManager.cs index 1790a4d3..7e458914 100644 --- a/src/PowerShell/PowerShellManager.cs +++ b/src/PowerShell/PowerShellManager.cs @@ -199,6 +199,7 @@ public Hashtable InvokeFunction( AzFunctionInfo functionInfo, Hashtable triggerMetadata, TraceContext traceContext, + RetryContext retryContext, IList inputData, FunctionInvocationPerformanceStopwatch stopwatch) { @@ -213,7 +214,7 @@ public Hashtable InvokeFunction( AddEntryPointInvocationCommand(functionInfo); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.FunctionCodeReady); - SetInputBindingParameterValues(functionInfo, inputData, durableController, triggerMetadata, traceContext); + SetInputBindingParameterValues(functionInfo, inputData, durableController, triggerMetadata, traceContext, retryContext); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.InputBindingValuesReady); if (!durableController.ShouldSuppressPipelineTraces()) @@ -258,7 +259,8 @@ private void SetInputBindingParameterValues( IEnumerable inputData, DurableController durableController, Hashtable triggerMetadata, - TraceContext traceContext) + TraceContext traceContext, + RetryContext retryContext) { foreach (var binding in inputData) { @@ -284,6 +286,11 @@ private void SetInputBindingParameterValues( { _pwsh.AddParameter(AzFunctionInfo.TraceContext, traceContext); } + + if (functionInfo.HasRetryContextParam) + { + _pwsh.AddParameter(AzFunctionInfo.RetryContext, retryContext); + } } /// diff --git a/src/Public/RetryContext.cs b/src/Public/RetryContext.cs new file mode 100644 index 00000000..e20df9af --- /dev/null +++ b/src/Public/RetryContext.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.Azure.WebJobs.Script.Grpc.Messages; + + +namespace Microsoft.Azure.Functions.PowerShellWorker +{ + /// + /// Custom RetryContext constructed from the RpcTraceContext member received from the host. + /// + internal class RetryContext + { + public RetryContext(int retryCount, int maxRetryCount, RpcException exception) + { + RetryCount = retryCount; + MaxRetryCount = maxRetryCount; + Exception = exception; + } + + public int RetryCount { get; } + + public int MaxRetryCount { get; } + + public RpcException Exception { get; } + } +} diff --git a/src/RequestProcessor.cs b/src/RequestProcessor.cs index 0cf96b8d..6aabd3f3 100644 --- a/src/RequestProcessor.cs +++ b/src/RequestProcessor.cs @@ -329,9 +329,10 @@ private Hashtable InvokeFunction( { var triggerMetadata = GetTriggerMetadata(functionInfo, invocationRequest); var traceContext = GetTraceContext(functionInfo, invocationRequest); + var retryContext = GetRetryContext(functionInfo, invocationRequest); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.MetadataAndTraceContextReady); - return psManager.InvokeFunction(functionInfo, triggerMetadata, traceContext, invocationRequest.InputData, stopwatch); + return psManager.InvokeFunction(functionInfo, triggerMetadata, traceContext, retryContext, invocationRequest.InputData, stopwatch); } internal StreamingMessage ProcessInvocationCancelRequest(StreamingMessage request) @@ -452,6 +453,19 @@ private static TraceContext GetTraceContext(AzFunctionInfo functionInfo, Invocat invocationRequest.TraceContext.Attributes); } + private static RetryContext GetRetryContext(AzFunctionInfo functionInfo, InvocationRequest invocationRequest) + { + if (!functionInfo.HasRetryContextParam) + { + return null; + } + + return new RetryContext( + invocationRequest.RetryContext.RetryCount, + invocationRequest.RetryContext.MaxRetryCount, + invocationRequest.RetryContext.Exception); + } + /// /// Set the 'ReturnValue' and 'OutputData' based on the invocation results appropriately. /// diff --git a/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs b/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs new file mode 100644 index 00000000..10c4fbb4 --- /dev/null +++ b/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Azure.Functions.PowerShell.Tests.E2E +{ + [Collection(Constants.FunctionAppCollectionName)] + public class RetryContextEndToEndTests + { + private readonly FunctionAppFixture _fixture; + + public RetryContextEndToEndTests(FunctionAppFixture fixture) + { + this._fixture = fixture; + } + + [Theory] + [InlineData("HttpTriggerThrowsWithFixedRetry", "", HttpStatusCode.InternalServerError, "Current retry count: 3")] + public async Task HttpTriggerRetryContextTests(string functionName, string queryString, HttpStatusCode expectedStatusCode, string expectedMessage) + { + Assert.True(await Utilities.InvokeHttpTrigger(functionName, queryString, expectedStatusCode, expectedMessage)); + } + } +} diff --git a/test/Unit/PowerShell/PowerShellManagerTests.cs b/test/Unit/PowerShell/PowerShellManagerTests.cs index 7fb062aa..0246afc9 100644 --- a/test/Unit/PowerShell/PowerShellManagerTests.cs +++ b/test/Unit/PowerShell/PowerShellManagerTests.cs @@ -391,7 +391,7 @@ internal void SuppressPipelineTracesForDurableActivityFunctionOnly(DurableFuncti { FunctionMetadata.RegisterFunctionMetadata(testManager.InstanceId, functionInfo.OutputBindings); - var result = testManager.InvokeFunction(functionInfo, null, null, CreateOrchestratorInputData(), new FunctionInvocationPerformanceStopwatch()); + var result = testManager.InvokeFunction(functionInfo, null, null, null, CreateOrchestratorInputData(), new FunctionInvocationPerformanceStopwatch()); var relevantLogs = s_testLogger.FullLog.Where(message => message.StartsWith("Information: OUTPUT:")).ToList(); var expected = shouldSuppressPipelineTraces ? new string[0] : new[] { "Information: OUTPUT: Hello" }; @@ -423,7 +423,7 @@ private static List CreateOrchestratorInputData() private static Hashtable InvokeFunction(PowerShellManager powerShellManager, AzFunctionInfo functionInfo, Hashtable triggerMetadata = null) { - return powerShellManager.InvokeFunction(functionInfo, triggerMetadata, null, s_testInputData, new FunctionInvocationPerformanceStopwatch()); + return powerShellManager.InvokeFunction(functionInfo, triggerMetadata, null, null, s_testInputData, new FunctionInvocationPerformanceStopwatch()); } private class ContextValidatingLogger : ILogger From fd1afa5150c1e02ac71462c229161b9725e9e74c Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Tue, 30 Nov 2021 18:20:37 -0800 Subject: [PATCH 3/7] Added tests --- src/Public/RetryContext.cs | 2 +- .../function.json | 25 +++++++++++++++++++ .../HttpTriggerThrowsWithFixedRetry/run.ps1 | 6 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json create mode 100644 test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 diff --git a/src/Public/RetryContext.cs b/src/Public/RetryContext.cs index e20df9af..fd241db8 100644 --- a/src/Public/RetryContext.cs +++ b/src/Public/RetryContext.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker { /// - /// Custom RetryContext constructed from the RpcTraceContext member received from the host. + /// Custom RetryContext constructed from the RpcRetryContext member received from the host. /// internal class RetryContext { diff --git a/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json b/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json new file mode 100644 index 00000000..23622817 --- /dev/null +++ b/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json @@ -0,0 +1,25 @@ +{ + "disabled": false, + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "res" + } + ], + "retry": { + "strategy": "fixedDelay", + "maxRetryCount": 4, + "delayInterval": "00:00:05" + } +} diff --git a/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 b/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 new file mode 100644 index 00000000..7e49ba8f --- /dev/null +++ b/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 @@ -0,0 +1,6 @@ +param($req, $retryContext) + +Write-Host "PowerShell HTTP trigger function processed a request." +Write-Host "Current retry count: $($retryContext.RetryCount)" + +throw "Test Exception" From 53d5dc69e3ce9cdcab659de6101e18f374cd155e Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Thu, 2 Dec 2021 09:35:46 -0800 Subject: [PATCH 4/7] Added unit tests and removed E2E tests --- src/Utility/TypeExtensions.cs | 4 +-- .../RetryContextEndToEndTests.cs | 27 ------------------- .../function.json | 25 ----------------- .../HttpTriggerThrowsWithFixedRetry/run.ps1 | 6 ----- test/Unit/Function/FunctionLoaderTests.cs | 22 ++++++++------- .../BasicFuncScriptWithRetryContext.ps1 | 8 ++++++ ...intAndTriggerMetadataAndRetryContext.psm1} | 2 +- .../Unit/PowerShell/PowerShellManagerTests.cs | 24 +++++++++++++---- ...ionWithTriggerMetadataAndRetryContext.ps1} | 9 +++++-- 9 files changed, 50 insertions(+), 77 deletions(-) delete mode 100644 test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs delete mode 100644 test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json delete mode 100644 test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 create mode 100644 test/Unit/Function/TestScripts/BasicFuncScriptWithRetryContext.ps1 rename test/Unit/Function/TestScripts/{FuncWithEntryPointAndTriggerMetadata.psm1 => FuncWithEntryPointAndTriggerMetadataAndRetryContext.psm1} (74%) rename test/Unit/PowerShell/TestScripts/{testBasicFunctionWithTriggerMetadata.ps1 => testBasicFunctionWithTriggerMetadataAndRetryContext.ps1} (56%) diff --git a/src/Utility/TypeExtensions.cs b/src/Utility/TypeExtensions.cs index 112b3c52..2f3c4186 100644 --- a/src/Utility/TypeExtensions.cs +++ b/src/Utility/TypeExtensions.cs @@ -153,9 +153,9 @@ internal static RpcException ToRpcException(this Exception exception) { return new RpcException { - Message = exception.Message, Source = exception.Source ?? "", - StackTrace = exception.StackTrace ?? "" + StackTrace = exception.StackTrace ?? "", + Message = exception.Message }; } diff --git a/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs b/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs deleted file mode 100644 index 10c4fbb4..00000000 --- a/test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/RetryContextEndToEndTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Net; -using System.Threading.Tasks; -using Xunit; - -namespace Azure.Functions.PowerShell.Tests.E2E -{ - [Collection(Constants.FunctionAppCollectionName)] - public class RetryContextEndToEndTests - { - private readonly FunctionAppFixture _fixture; - - public RetryContextEndToEndTests(FunctionAppFixture fixture) - { - this._fixture = fixture; - } - - [Theory] - [InlineData("HttpTriggerThrowsWithFixedRetry", "", HttpStatusCode.InternalServerError, "Current retry count: 3")] - public async Task HttpTriggerRetryContextTests(string functionName, string queryString, HttpStatusCode expectedStatusCode, string expectedMessage) - { - Assert.True(await Utilities.InvokeHttpTrigger(functionName, queryString, expectedStatusCode, expectedMessage)); - } - } -} diff --git a/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json b/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json deleted file mode 100644 index 23622817..00000000 --- a/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/function.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "disabled": false, - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ], - "retry": { - "strategy": "fixedDelay", - "maxRetryCount": 4, - "delayInterval": "00:00:05" - } -} diff --git a/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 b/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 deleted file mode 100644 index 7e49ba8f..00000000 --- a/test/E2E/TestFunctionApp/HttpTriggerThrowsWithFixedRetry/run.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -param($req, $retryContext) - -Write-Host "PowerShell HTTP trigger function processed a request." -Write-Host "Current retry count: $($retryContext.RetryCount)" - -throw "Test Exception" diff --git a/test/Unit/Function/FunctionLoaderTests.cs b/test/Unit/Function/FunctionLoaderTests.cs index 247f8a06..dc06479e 100644 --- a/test/Unit/Function/FunctionLoaderTests.cs +++ b/test/Unit/Function/FunctionLoaderTests.cs @@ -100,10 +100,12 @@ public void TestFunctionLoaderGetFuncWithRequires() Assert.Single(funcInfo.OutputBindings); } - [Fact] - public void TestFunctionLoaderGetFuncWithTriggerMetadataParam() + [Theory] + [InlineData("TriggerMetadata")] + [InlineData("RetryContext")] + public void TestFunctionLoaderGetFuncWithSingleParam(string paramName) { - var scriptFileToUse = Path.Join(_functionDirectory, "BasicFuncScriptWithTriggerMetadata.ps1"); + var scriptFileToUse = Path.Join(_functionDirectory, $"BasicFuncScriptWith{paramName}.ps1"); var entryPointToUse = string.Empty; var functionLoadRequest = GetFuncLoadRequest(scriptFileToUse, entryPointToUse); @@ -118,7 +120,7 @@ public void TestFunctionLoaderGetFuncWithTriggerMetadataParam() Assert.Equal(3, funcInfo.FuncParameters.Count); Assert.True(funcInfo.FuncParameters.ContainsKey("req")); Assert.True(funcInfo.FuncParameters.ContainsKey("inputBlob")); - Assert.True(funcInfo.FuncParameters.ContainsKey("TriggerMetadata")); + Assert.True(funcInfo.FuncParameters.ContainsKey(paramName)); Assert.Equal(3, funcInfo.AllBindings.Count); Assert.Equal(2, funcInfo.InputBindings.Count); @@ -229,10 +231,12 @@ public void ParametersShouldMatchInputBinding() Assert.Contains("inputBlob", exception.Message); } - [Fact] - public void ParametersShouldMatchInputBindingWithTriggerMetadataParam() + [Theory] + [InlineData("TriggerMetadata")] + [InlineData("RetryContext")] + public void ParametersShouldMatchInputBindingWithSingleParam(string paramName) { - var scriptFileToUse = Path.Join(_functionDirectory, "BasicFuncScriptWithTriggerMetadata.ps1"); + var scriptFileToUse = Path.Join(_functionDirectory, $"BasicFuncScriptWith{paramName}.ps1"); var entryPointToUse = string.Empty; var functionLoadRequest = GetFuncLoadRequest(scriptFileToUse, entryPointToUse); @@ -262,9 +266,9 @@ public void EntryPointParametersShouldMatchInputBinding() } [Fact] - public void EntryPointParametersShouldMatchInputBindingWithTriggerMetadataParam() + public void EntryPointParametersShouldMatchInputBindingWithTriggerMetadataAndRetryContextParams() { - var scriptFileToUse = Path.Join(_functionDirectory, "FuncWithEntryPointAndTriggerMetadata.psm1"); + var scriptFileToUse = Path.Join(_functionDirectory, "FuncWithEntryPointAndTriggerMetadataAndRetryContext.psm1"); var entryPointToUse = "Run"; var functionLoadRequest = GetFuncLoadRequest(scriptFileToUse, entryPointToUse); diff --git a/test/Unit/Function/TestScripts/BasicFuncScriptWithRetryContext.ps1 b/test/Unit/Function/TestScripts/BasicFuncScriptWithRetryContext.ps1 new file mode 100644 index 00000000..167e7f97 --- /dev/null +++ b/test/Unit/Function/TestScripts/BasicFuncScriptWithRetryContext.ps1 @@ -0,0 +1,8 @@ +# +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +param($req, $inputBlob, $RetryContext) + +"DoNothing" diff --git a/test/Unit/Function/TestScripts/FuncWithEntryPointAndTriggerMetadata.psm1 b/test/Unit/Function/TestScripts/FuncWithEntryPointAndTriggerMetadataAndRetryContext.psm1 similarity index 74% rename from test/Unit/Function/TestScripts/FuncWithEntryPointAndTriggerMetadata.psm1 rename to test/Unit/Function/TestScripts/FuncWithEntryPointAndTriggerMetadataAndRetryContext.psm1 index 0ff1ab86..418f8d99 100644 --- a/test/Unit/Function/TestScripts/FuncWithEntryPointAndTriggerMetadata.psm1 +++ b/test/Unit/Function/TestScripts/FuncWithEntryPointAndTriggerMetadataAndRetryContext.psm1 @@ -5,7 +5,7 @@ function Run { - param($req, $inputBlob, $TriggerMetadata) + param($req, $inputBlob, $TriggerMetadata, $RetryContext) "Run" } diff --git a/test/Unit/PowerShell/PowerShellManagerTests.cs b/test/Unit/PowerShell/PowerShellManagerTests.cs index 0246afc9..727fd879 100644 --- a/test/Unit/PowerShell/PowerShellManagerTests.cs +++ b/test/Unit/PowerShell/PowerShellManagerTests.cs @@ -38,6 +38,14 @@ public class PowerShellManagerTests : IDisposable private const string TestInputBindingName = "req"; private const string TestOutputBindingName = "res"; private const string TestStringData = "Foo"; + private const int TestRetryCount = 0; + private const int TestMaxRetryCount = 1; + private readonly static RpcException TestException = new RpcException + { + Source = "", + StackTrace = "", + Message = "TestMessage" + }; private readonly static string s_funcDirectory; private readonly static FunctionLoadRequest s_functionLoadRequest; @@ -176,9 +184,9 @@ public void InvokeBasicFunctionWithRequiresWorks() } [Fact] - public void InvokeBasicFunctionWithTriggerMetadataAndTraceContextWorks() + public void InvokeBasicFunctionWithTriggerMetadataAndTraceContextAndRetryContextWorks() { - string path = Path.Join(s_funcDirectory, "testBasicFunctionWithTriggerMetadata.ps1"); + string path = Path.Join(s_funcDirectory, "testBasicFunctionWithTriggerMetadataAndRetryContext.ps1"); var (functionInfo, testManager) = PrepareFunction(path, string.Empty); Hashtable triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase) @@ -186,11 +194,13 @@ public void InvokeBasicFunctionWithTriggerMetadataAndTraceContextWorks() { TestInputBindingName, TestStringData } }; + RetryContext retryContext = new RetryContext(TestRetryCount, TestMaxRetryCount, TestException); + try { FunctionMetadata.RegisterFunctionMetadata(testManager.InstanceId, functionInfo.OutputBindings); - Hashtable result = InvokeFunction(testManager, functionInfo, triggerMetadata); + Hashtable result = InvokeFunction(testManager, functionInfo, triggerMetadata, retryContext); // The outputBinding hashtable for the runspace should be cleared after 'InvokeFunction' Hashtable outputBindings = FunctionMetadata.GetOutputBindingHashtable(testManager.InstanceId); @@ -421,9 +431,13 @@ private static List CreateOrchestratorInputData() return testInputData; } - private static Hashtable InvokeFunction(PowerShellManager powerShellManager, AzFunctionInfo functionInfo, Hashtable triggerMetadata = null) + private static Hashtable InvokeFunction( + PowerShellManager powerShellManager, + AzFunctionInfo functionInfo, + Hashtable triggerMetadata = null, + RetryContext retryContext = null) { - return powerShellManager.InvokeFunction(functionInfo, triggerMetadata, null, null, s_testInputData, new FunctionInvocationPerformanceStopwatch()); + return powerShellManager.InvokeFunction(functionInfo, triggerMetadata, null, retryContext, s_testInputData, new FunctionInvocationPerformanceStopwatch()); } private class ContextValidatingLogger : ILogger diff --git a/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadata.ps1 b/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 similarity index 56% rename from test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadata.ps1 rename to test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 index 401399c4..ed6ead63 100644 --- a/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadata.ps1 +++ b/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 @@ -3,11 +3,16 @@ # Licensed under the MIT license. See LICENSE file in the project root for full license information. # -param ($Req, $TriggerMetadata) +param ($Req, $TriggerMetadata, $RetryContext) # Used for logging tests Write-Verbose "a log" $cmdName = $MyInvocation.MyCommand.Name -$result = "{0},{1}" -f $TriggerMetadata.Req, $cmdName +$result = "{0},{1}:{2},{3}" -f ` + $TriggerMetadata.Req,` + $cmdName,` + $RetryContext.RetryCount,` + $RetryContext.MaxRetryCount,` + $RetryContext.Exception.Message Push-OutputBinding -Name res -Value $result From 713937a138e60b8801281c343c0dd9d4efa9bcaa Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Thu, 2 Dec 2021 10:10:37 -0800 Subject: [PATCH 5/7] Fix RetryContext unit test --- test/Unit/PowerShell/PowerShellManagerTests.cs | 5 +++-- .../testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/Unit/PowerShell/PowerShellManagerTests.cs b/test/Unit/PowerShell/PowerShellManagerTests.cs index 727fd879..5d4faf42 100644 --- a/test/Unit/PowerShell/PowerShellManagerTests.cs +++ b/test/Unit/PowerShell/PowerShellManagerTests.cs @@ -40,11 +40,12 @@ public class PowerShellManagerTests : IDisposable private const string TestStringData = "Foo"; private const int TestRetryCount = 0; private const int TestMaxRetryCount = 1; + private const string TestMessage = "TestMessage"; private readonly static RpcException TestException = new RpcException { Source = "", StackTrace = "", - Message = "TestMessage" + Message = TestMessage }; private readonly static string s_funcDirectory; @@ -207,7 +208,7 @@ public void InvokeBasicFunctionWithTriggerMetadataAndTraceContextAndRetryContext Assert.Empty(outputBindings); // A PowerShell function should be created fro the Az function. - string expectedResult = $"{TestStringData},{functionInfo.DeployedPSFuncName}"; + string expectedResult = $"{TestStringData},{functionInfo.DeployedPSFuncName}:{TestRetryCount},{TestMaxRetryCount},{TestMessage}"; Assert.Equal(expectedResult, result[TestOutputBindingName]); } finally diff --git a/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 b/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 index ed6ead63..4c10f0ad 100644 --- a/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 +++ b/test/Unit/PowerShell/TestScripts/testBasicFunctionWithTriggerMetadataAndRetryContext.ps1 @@ -9,7 +9,7 @@ param ($Req, $TriggerMetadata, $RetryContext) Write-Verbose "a log" $cmdName = $MyInvocation.MyCommand.Name -$result = "{0},{1}:{2},{3}" -f ` +$result = "{0},{1}:{2},{3},{4}" -f ` $TriggerMetadata.Req,` $cmdName,` $RetryContext.RetryCount,` From 6b87f6563b93974d00e26f563dd61de64f9f495e Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Thu, 2 Dec 2021 10:32:29 -0800 Subject: [PATCH 6/7] Updated release_notes.md --- release_notes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index c636147d..b789fae4 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1 +1,2 @@ -* Bug fix: Activity Functions can now use output bindings (https://github.com/Azure/azure-functions-powershell-worker/issues/646) \ No newline at end of file +* Bug fix: Activity Functions can now use output bindings (https://github.com/Azure/azure-functions-powershell-worker/issues/646) +* New feature: Added support for access to RetryContext within execution context (https://github.com/Azure/azure-functions-powershell-worker/issues/594) \ No newline at end of file From 51126d453d9f0420f6870361f82982f43bb745b8 Mon Sep 17 00:00:00 2001 From: Michael Peng Date: Thu, 2 Dec 2021 13:19:21 -0800 Subject: [PATCH 7/7] Addressing requested changes --- release_notes.md | 3 +-- src/Public/RetryContext.cs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/release_notes.md b/release_notes.md index b789fae4..c636147d 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,2 +1 @@ -* Bug fix: Activity Functions can now use output bindings (https://github.com/Azure/azure-functions-powershell-worker/issues/646) -* New feature: Added support for access to RetryContext within execution context (https://github.com/Azure/azure-functions-powershell-worker/issues/594) \ No newline at end of file +* Bug fix: Activity Functions can now use output bindings (https://github.com/Azure/azure-functions-powershell-worker/issues/646) \ No newline at end of file diff --git a/src/Public/RetryContext.cs b/src/Public/RetryContext.cs index fd241db8..4876a4fa 100644 --- a/src/Public/RetryContext.cs +++ b/src/Public/RetryContext.cs @@ -5,7 +5,6 @@ using Microsoft.Azure.WebJobs.Script.Grpc.Messages; - namespace Microsoft.Azure.Functions.PowerShellWorker { ///