/// <summary> /// Checkout an idle PowerShellManager instance in a non-blocking asynchronous way. /// </summary> internal PowerShellManager CheckoutIdleWorker(StreamingMessage request, AzFunctionInfo functionInfo) { PowerShellManager psManager = null; string requestId = request.RequestId; string invocationId = request.InvocationRequest?.InvocationId; // If the pool has an idle one, just use it. if (!_pool.TryTake(out psManager)) { // The pool doesn't have an idle one. if (_poolSize < _upperBound && Interlocked.Increment(ref _poolSize) <= _upperBound) { // If the pool hasn't reached its bounded capacity yet, then // we create a new item and return it. var logger = new RpcLogger(_msgStream); logger.SetContext(requestId, invocationId); psManager = new PowerShellManager(logger); RpcLogger.WriteSystemLog(string.Format(PowerShellWorkerStrings.LogNewPowerShellManagerCreated, _poolSize.ToString())); } else { // If the pool has reached its bounded capacity, then the thread // should be blocked until an idle one becomes available. psManager = _pool.Take(); } } // Register the function with the Runspace before returning the idle PowerShellManager. FunctionMetadata.RegisterFunctionMetadata(psManager.InstanceId, functionInfo); psManager.Logger.SetContext(requestId, invocationId); return(psManager); }
private void SetInputBindingParameterValues( AzFunctionInfo functionInfo, IEnumerable <ParameterBinding> inputData, DurableController durableController, Hashtable triggerMetadata, TraceContext traceContext) { foreach (var binding in inputData) { if (functionInfo.FuncParameters.TryGetValue(binding.Name, out var paramInfo)) { if (!durableController.TryGetInputBindingParameterValue(binding.Name, out var valueToUse)) { var bindingInfo = functionInfo.InputBindings[binding.Name]; valueToUse = Utils.TransformInBindingValueAsNeeded(paramInfo, bindingInfo, binding.Data.ToObject()); } _pwsh.AddParameter(binding.Name, valueToUse); } } // Gives access to additional Trigger Metadata if the user specifies TriggerMetadata if (functionInfo.HasTriggerMetadataParam) { _pwsh.AddParameter(AzFunctionInfo.TriggerMetadata, triggerMetadata); } if (functionInfo.HasTraceContextParam) { _pwsh.AddParameter(AzFunctionInfo.TraceContext, traceContext); } }
/// <summary> /// Helper method to set the output binding metadata for the function that is about to run. /// </summary> internal void RegisterFunctionMetadata(AzFunctionInfo functionInfo) { var outputBindings = functionInfo.OutputBindings; FunctionMetadata.OutputBindingCache.AddOrUpdate(_pwsh.Runspace.InstanceId, outputBindings, (key, value) => outputBindings); }
private static Hashtable InvokeFunction( PowerShellManager powerShellManager, AzFunctionInfo functionInfo, Hashtable triggerMetadata = null, RetryContext retryContext = null) { return(powerShellManager.InvokeFunction(functionInfo, triggerMetadata, null, retryContext, s_testInputData, new FunctionInvocationPerformanceStopwatch())); }
public Hashtable InvokeFunction( AzFunctionInfo functionInfo, Hashtable triggerMetadata, TraceContext traceContext, RetryContext retryContext, IList <ParameterBinding> inputData, FunctionInvocationPerformanceStopwatch stopwatch) { var outputBindings = FunctionMetadata.GetOutputBindingHashtable(_pwsh.Runspace.InstanceId); var durableController = new DurableController(functionInfo.DurableFunctionInfo, _pwsh); try { durableController.BeforeFunctionInvocation(inputData); AddEntryPointInvocationCommand(functionInfo); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.FunctionCodeReady); SetInputBindingParameterValues(functionInfo, inputData, durableController, 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)); } catch (RuntimeException e) { ErrorAnalysisLogger.Log(Logger, e.ErrorRecord, isException: true); Logger.Log(isUserOnlyLog: true, LogLevel.Error, GetFunctionExceptionMessage(e)); throw; } catch (OrchestrationFailureException e) { if (e.InnerException is IContainsErrorRecord inner) { Logger.Log(isUserOnlyLog: true, LogLevel.Error, GetFunctionExceptionMessage(inner)); } throw; } } finally { durableController.AfterFunctionInvocation(); outputBindings.Clear(); ResetRunspace(); } }
internal Hashtable InvokeOrchestrationFunction( AzFunctionInfo functionInfo, string contextParamName, OrchestrationContext context) { context.ActionEvent = _actionEvent; try { if (!functionInfo.FuncParameters.ContainsKey(contextParamName)) { // TODO: we should do more analysis in FunctionLoadRequest phase throw new InvalidOperationException($"Orchestration function should define the parameter '-{contextParamName}'"); } _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Set-FunctionInvocationContext") .AddParameter("OrchestrationContext", context) .InvokeAndClearCommands(); _pwsh.AddCommand(string.IsNullOrEmpty(functionInfo.EntryPoint) ? functionInfo.ScriptPath : functionInfo.EntryPoint) .AddParameter(contextParamName, context); var outputBuffer = new PSDataCollection <object>(); var asyncResult = _pwsh.BeginInvoke <object, object>(input: null, output: outputBuffer); var waitHandles = new[] { _actionEvent, asyncResult.AsyncWaitHandle }; var signaledHandleIndex = WaitHandle.WaitAny(waitHandles); var signaledHandle = waitHandles[signaledHandleIndex]; OrchestrationMessage retResult = null; if (ReferenceEquals(signaledHandle, _actionEvent)) { // _actionEvent signaled _pwsh.Stop(); retResult = new OrchestrationMessage(isDone: false, actions: context.Actions, output: null); } else { // asyncResult.AsyncWaitHandle signaled _pwsh.EndInvoke(asyncResult); var result = CreateReturnValueFromFunctionOutput(outputBuffer); retResult = new OrchestrationMessage(isDone: true, actions: context.Actions, output: result); } return(new Hashtable { { AzFunctionInfo.DollarReturn, retResult } }); } finally { _pwsh.Streams.ClearStreams(); _pwsh.Commands.Clear(); ClearInvocationContext(); ResetRunspace(); } }
private void AddEntryPointInvocationCommand(AzFunctionInfo functionInfo) { if (string.IsNullOrEmpty(functionInfo.EntryPoint)) { _pwsh.AddCommand(functionInfo.DeployedPSFuncName ?? functionInfo.ScriptPath); } else { // If an entry point is defined, we import the script module. _pwsh.AddCommand(Utils.ImportModuleCmdletInfo) .AddParameter("Name", functionInfo.ScriptPath) .InvokeAndClearCommands(); _pwsh.AddCommand(functionInfo.EntryPoint); } }
/// <summary> /// Checkout an idle PowerShellManager instance in a non-blocking asynchronous way. /// </summary> internal PowerShellManager CheckoutIdleWorker(StreamingMessage request, AzFunctionInfo functionInfo) { PowerShellManager psManager = null; string requestId = request.RequestId; string invocationId = request.InvocationRequest?.InvocationId; // If the pool has an idle one, just use it. if (!_pool.TryTake(out psManager)) { // The pool doesn't have an idle one. if (_poolSize < UpperBound) { int id = Interlocked.Increment(ref _poolSize); if (id <= UpperBound) { // If the pool hasn't reached its bounded capacity yet, then // we create a new item and return it. var logger = CreateLoggerWithContext(requestId, invocationId); psManager = new PowerShellManager(logger, id); RpcLogger.WriteSystemLog(LogLevel.Trace, string.Format(PowerShellWorkerStrings.LogNewPowerShellManagerCreated, id.ToString())); } } if (psManager == null) { var logger = CreateLoggerWithContext(requestId, invocationId); logger.Log(isUserOnlyLog: true, LogLevel.Warning, string.Format(PowerShellWorkerStrings.FunctionQueuingRequest, functionInfo.FuncName)); // If the pool has reached its bounded capacity, then the thread // should be blocked until an idle one becomes available. psManager = _pool.Take(); } } // Finish the initialization if not yet. // This applies only to the very first PowerShellManager instance, whose initialization was deferred. psManager.Initialize(); // Register the function with the Runspace before returning the idle PowerShellManager. FunctionMetadata.RegisterFunctionMetadata(psManager.InstanceId, functionInfo); psManager.Logger.SetContext(requestId, invocationId); return(psManager); }
static HelperModuleTests() { var funcDirectory = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "TestScripts", "PowerShell"); var rpcFuncMetadata = new RpcFunctionMetadata() { Name = "TestFuncApp", Directory = funcDirectory, ScriptFile = Path.Join(funcDirectory, "testBasicFunction.ps1"), EntryPoint = string.Empty, Bindings = { { "req", new BindingInfo { Direction = BindingInfo.Types.Direction.In, Type = "httpTrigger" } }, { Response, new BindingInfo { Direction = BindingInfo.Types.Direction.Out, Type = "http" } }, { Queue, new BindingInfo { Direction = BindingInfo.Types.Direction.Out, Type = "queue" } }, { Foo, new BindingInfo { Direction = BindingInfo.Types.Direction.Out, Type = "new" } }, { Bar, new BindingInfo { Direction = BindingInfo.Types.Direction.Out, Type = "new" } }, { Food, new BindingInfo { Direction = BindingInfo.Types.Direction.Out, Type = "new" } } } }; var funcLoadReq = new FunctionLoadRequest { FunctionId = "FunctionId", Metadata = rpcFuncMetadata }; FunctionLoader.SetupWellKnownPaths(funcLoadReq); s_pwsh = Utils.NewPwshInstance(); s_funcInfo = new AzFunctionInfo(rpcFuncMetadata); }
/// <summary> /// Execution a function fired by a trigger or an activity function scheduled by an orchestration. /// </summary> internal Hashtable InvokeFunction( AzFunctionInfo functionInfo, Hashtable triggerMetadata, IList <ParameterBinding> inputData) { string scriptPath = functionInfo.ScriptPath; string entryPoint = functionInfo.EntryPoint; string moduleName = null; try { // If an entry point is defined, we load the script as a module and invoke the function with that name. // We also need to fetch the ParameterMetadata to know what to pass in as arguments. var parameterMetadata = RetriveParameterMetadata(functionInfo, out moduleName); _pwsh.AddCommand(String.IsNullOrEmpty(entryPoint) ? scriptPath : entryPoint); // Set arguments for each input binding parameter foreach (ParameterBinding binding in inputData) { if (parameterMetadata.ContainsKey(binding.Name)) { _pwsh.AddParameter(binding.Name, binding.Data.ToObject()); } } // Gives access to additional Trigger Metadata if the user specifies TriggerMetadata if (parameterMetadata.ContainsKey(AzFunctionInfo.TriggerMetadata)) { _logger.Log(LogLevel.Debug, "Parameter '-TriggerMetadata' found."); _pwsh.AddParameter(AzFunctionInfo.TriggerMetadata, triggerMetadata); } Collection <object> pipelineItems = null; using (ExecutionTimer.Start(_logger, "Execution of the user's function completed.")) { pipelineItems = _pwsh.InvokeAndClearCommands <object>(); } var result = _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Get-OutputBinding") .AddParameter("Purge") .InvokeAndClearCommands <Hashtable>()[0]; if (pipelineItems != null && pipelineItems.Count > 0) { // Log everything we received from the pipeline foreach (var item in pipelineItems) { _logger.Log(LogLevel.Information, $"OUTPUT: {_pwsh.FormatObjectToString(item)}", isUserLog: true); } // TODO: See GitHub issue #82. We are not settled on how to handle the Azure Functions concept of the $returns Output Binding // If we would like to support Option 1 from #82, uncomment the following code: // object[] items = new object[pipelineItems.Count]; // pipelineItems.CopyTo(items, 0); // result.Add(AzFunctionInfo.DollarReturn, items); // If we would like to support Option 2 from #82, uncomment this line: // result.Add(AzFunctionInfo.DollarReturn, pipelineItems[pipelineItems.Count - 1]); } return(result); } finally { ResetRunspace(moduleName); } }
/// <summary> /// Execution a function fired by a trigger or an activity function scheduled by an orchestration. /// </summary> internal Hashtable InvokeFunction( AzFunctionInfo functionInfo, Hashtable triggerMetadata, TraceContext traceContext, IList <ParameterBinding> inputData, FunctionInvocationPerformanceStopwatch stopwatch) { string scriptPath = functionInfo.ScriptPath; string entryPoint = functionInfo.EntryPoint; var hasSetInvocationContext = false; Hashtable outputBindings = FunctionMetadata.GetOutputBindingHashtable(_pwsh.Runspace.InstanceId); try { if (Utils.AreDurableFunctionsEnabled()) { // If the function has a output binding of the 'orchestrationClient' type, then we set the binding name // in the module context for the 'Start-NewOrchestration' function to use. if (!string.IsNullOrEmpty(functionInfo.OrchestrationClientBindingName)) { _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Set-FunctionInvocationContext") .AddParameter("OrchestrationStarter", functionInfo.OrchestrationClientBindingName) .InvokeAndClearCommands(); hasSetInvocationContext = true; } } if (string.IsNullOrEmpty(entryPoint)) { _pwsh.AddCommand(functionInfo.DeployedPSFuncName ?? scriptPath); } else { // If an entry point is defined, we import the script module. _pwsh.AddCommand(Utils.ImportModuleCmdletInfo) .AddParameter("Name", scriptPath) .InvokeAndClearCommands(); _pwsh.AddCommand(entryPoint); } stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.FunctionCodeReady); // Set arguments for each input binding parameter foreach (ParameterBinding binding in inputData) { string bindingName = binding.Name; if (functionInfo.FuncParameters.TryGetValue(bindingName, out PSScriptParamInfo paramInfo)) { var bindingInfo = functionInfo.InputBindings[bindingName]; var valueToUse = Utils.TransformInBindingValueAsNeeded(paramInfo, bindingInfo, binding.Data.ToObject()); _pwsh.AddParameter(bindingName, valueToUse); } } stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.InputBindingValuesReady); // Gives access to additional Trigger Metadata if the user specifies TriggerMetadata if (functionInfo.HasTriggerMetadataParam) { _pwsh.AddParameter(AzFunctionInfo.TriggerMetadata, triggerMetadata); } if (functionInfo.HasTraceContextParam) { _pwsh.AddParameter(AzFunctionInfo.TraceContext, traceContext); } _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject"); stopwatch.OnCheckpoint(FunctionInvocationPerformanceStopwatch.Checkpoint.InvokingFunctionCode); Logger.Log(isUserOnlyLog: false, LogLevel.Trace, CreateInvocationPerformanceReportMessage(functionInfo.FuncName, stopwatch)); Collection <object> pipelineItems = null; try { pipelineItems = _pwsh.InvokeAndClearCommands <object>(); } catch (RuntimeException e) { if (e.ErrorRecord.FullyQualifiedErrorId == "CommandNotFoundException") { Logger.Log(isUserOnlyLog: false, LogLevel.Warning, PowerShellWorkerStrings.CommandNotFoundException_Exception); } Logger.Log(isUserOnlyLog: true, LogLevel.Error, GetFunctionExceptionMessage(e)); throw; } Hashtable result = new Hashtable(outputBindings, StringComparer.OrdinalIgnoreCase); if (Utils.AreDurableFunctionsEnabled() && functionInfo.Type == AzFunctionType.ActivityFunction) { var returnValue = CreateReturnValueFromFunctionOutput(pipelineItems); result.Add(AzFunctionInfo.DollarReturn, returnValue); } return(result); } finally { if (hasSetInvocationContext) { ClearInvocationContext(); } outputBindings.Clear(); ResetRunspace(); } }
/// <summary> /// Execution a function fired by a trigger or an activity function scheduled by an orchestration. /// </summary> internal Hashtable InvokeFunction( AzFunctionInfo functionInfo, Hashtable triggerMetadata, IList <ParameterBinding> inputData) { string scriptPath = functionInfo.ScriptPath; string entryPoint = functionInfo.EntryPoint; string moduleName = null; try { if (string.IsNullOrEmpty(entryPoint)) { _pwsh.AddCommand(scriptPath); } else { // If an entry point is defined, we import the script module. moduleName = Path.GetFileNameWithoutExtension(scriptPath); _pwsh.AddCommand(Utils.ImportModuleCmdletInfo) .AddParameter("Name", scriptPath) .InvokeAndClearCommands(); _pwsh.AddCommand(entryPoint); } // Set arguments for each input binding parameter foreach (ParameterBinding binding in inputData) { string bindingName = binding.Name; if (functionInfo.FuncParameters.TryGetValue(bindingName, out PSScriptParamInfo paramInfo)) { var bindingInfo = functionInfo.InputBindings[bindingName]; var valueToUse = Utils.TransformInBindingValueAsNeeded(paramInfo, bindingInfo, binding.Data.ToObject()); _pwsh.AddParameter(bindingName, valueToUse); } } // Gives access to additional Trigger Metadata if the user specifies TriggerMetadata if (functionInfo.HasTriggerMetadataParam) { _pwsh.AddParameter(AzFunctionInfo.TriggerMetadata, triggerMetadata); } Collection <object> pipelineItems = _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject") .InvokeAndClearCommands <object>(); Hashtable result = _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Get-OutputBinding") .AddParameter("Purge", true) .InvokeAndClearCommands <Hashtable>()[0]; /* * TODO: See GitHub issue #82. We are not settled on how to handle the Azure Functions concept of the $returns Output Binding * if (pipelineItems != null && pipelineItems.Count > 0) * { * // If we would like to support Option 1 from #82, use the following 3 lines of code: * object[] items = new object[pipelineItems.Count]; * pipelineItems.CopyTo(items, 0); * result.Add(AzFunctionInfo.DollarReturn, items); * * // If we would like to support Option 2 from #82, use this line: * result.Add(AzFunctionInfo.DollarReturn, pipelineItems[pipelineItems.Count - 1]); * } */ return(result); } finally { ResetRunspace(moduleName); } }
/// <summary> /// Execution a function fired by a trigger or an activity function scheduled by an orchestration. /// </summary> internal Hashtable InvokeFunction( AzFunctionInfo functionInfo, Hashtable triggerMetadata, IList <ParameterBinding> inputData) { string scriptPath = functionInfo.ScriptPath; string entryPoint = functionInfo.EntryPoint; string moduleName = null; try { if (string.IsNullOrEmpty(entryPoint)) { _pwsh.AddCommand(scriptPath); } else { // If an entry point is defined, we import the script module. moduleName = Path.GetFileNameWithoutExtension(scriptPath); _pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module") .AddParameter("Name", scriptPath) .InvokeAndClearCommands(); _pwsh.AddCommand(entryPoint); } // Set arguments for each input binding parameter foreach (ParameterBinding binding in inputData) { if (functionInfo.FuncParameters.Contains(binding.Name)) { _pwsh.AddParameter(binding.Name, binding.Data.ToObject()); } } // Gives access to additional Trigger Metadata if the user specifies TriggerMetadata if (functionInfo.FuncParameters.Contains(AzFunctionInfo.TriggerMetadata)) { _logger.Log(LogLevel.Debug, "Parameter '-TriggerMetadata' found."); _pwsh.AddParameter(AzFunctionInfo.TriggerMetadata, triggerMetadata); } Collection <object> pipelineItems = null; using (ExecutionTimer.Start(_logger, "Execution of the user's function completed.")) { pipelineItems = _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject") .InvokeAndClearCommands <object>(); } var result = _pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Get-OutputBinding") .AddParameter("Purge", true) .InvokeAndClearCommands <Hashtable>()[0]; /* * TODO: See GitHub issue #82. We are not settled on how to handle the Azure Functions concept of the $returns Output Binding * if (pipelineItems != null && pipelineItems.Count > 0) * { * // If we would like to support Option 1 from #82, use the following 3 lines of code: * object[] items = new object[pipelineItems.Count]; * pipelineItems.CopyTo(items, 0); * result.Add(AzFunctionInfo.DollarReturn, items); * * // If we would like to support Option 2 from #82, use this line: * result.Add(AzFunctionInfo.DollarReturn, pipelineItems[pipelineItems.Count - 1]); * } */ return(result); } finally { ResetRunspace(moduleName); } }