Skip to content

New Programming Model #959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Licensed under the MIT license. See LICENSE file in the project root for full li
<NoBuild>true</NoBuild>
<IncludeBuildOutput>false</IncludeBuildOutput>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>NU5100;NU5123</NoWarn>
<NoWarn>NU5100;NU5110;NU5111;NU5123</NoWarn>

<NuspecFile>Microsoft.Azure.Functions.PowerShellWorker.nuspec</NuspecFile>
<NuspecProperties>configuration=$(Configuration);targetFramework=$(TargetFramework);version=$(Version)</NuspecProperties>
Expand Down
12 changes: 6 additions & 6 deletions src/DependencyManagement/DependencyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal class DependencyManager : IDisposable
#endregion

public DependencyManager(
string requestMetadataDirectory = null,
string functionAppRootPath = null,
IModuleProvider moduleProvider = null,
IDependencyManagerStorage storage = null,
IInstalledDependenciesLocator installedDependenciesLocator = null,
Expand All @@ -54,7 +54,7 @@ public DependencyManager(
IBackgroundDependencySnapshotContentLogger currentSnapshotContentLogger = null,
ILogger logger = null)
{
_storage = storage ?? new DependencyManagerStorage(GetFunctionAppRootPath(requestMetadataDirectory));
_storage = storage ?? new DependencyManagerStorage(GetFunctionAppRootPath(functionAppRootPath));
_installedDependenciesLocator = installedDependenciesLocator ?? new InstalledDependenciesLocator(_storage, logger);
var snapshotContentLogger = new PowerShellModuleSnapshotLogger();
_installer = installer ?? new DependencySnapshotInstaller(
Expand Down Expand Up @@ -257,14 +257,14 @@ private bool AreAcceptableDependenciesAlreadyInstalled()
return _storage.SnapshotExists(_currentSnapshotPath);
}

private static string GetFunctionAppRootPath(string requestMetadataDirectory)
private static string GetFunctionAppRootPath(string functionAppRootPath)
{
if (string.IsNullOrWhiteSpace(requestMetadataDirectory))
if (string.IsNullOrWhiteSpace(functionAppRootPath))
{
throw new ArgumentException("Empty request metadata directory path", nameof(requestMetadataDirectory));
throw new ArgumentException("Empty function app root path", nameof(functionAppRootPath));
}

return Path.GetFullPath(Path.Join(requestMetadataDirectory, ".."));
return functionAppRootPath;
}

#endregion
Expand Down
5 changes: 2 additions & 3 deletions src/FunctionLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@ internal static void ClearLoadedFunctions()
/// Setup the well known paths about the FunctionApp.
/// This method is called only once during the code start.
/// </summary>
internal static void SetupWellKnownPaths(FunctionLoadRequest request, string managedDependenciesPath)
internal static void SetupWellKnownPaths(string functionAppRootPath, string managedDependenciesPath)
{
// Resolve the FunctionApp root path
FunctionAppRootPath = Path.GetFullPath(Path.Join(request.Metadata.Directory, ".."));
FunctionAppRootPath = functionAppRootPath;

// Resolve module paths
var appLevelModulesPath = Path.Join(FunctionAppRootPath, "Modules");
Expand Down
57 changes: 52 additions & 5 deletions src/RequestProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation.Remoting;
Expand All @@ -18,7 +19,10 @@

namespace Microsoft.Azure.Functions.PowerShellWorker
{
using Microsoft.Azure.Functions.PowerShellWorker.WorkerIndexing;
using Microsoft.PowerShell;
using System.Diagnostics;
using System.Text.Json;
using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level;
using System.Runtime.InteropServices;

Expand All @@ -29,6 +33,7 @@ internal class RequestProcessor
private readonly PowerShellManagerPool _powershellPool;
private DependencyManager _dependencyManager;
private string _pwshVersion;
private string _functionAppRootPath;

// Holds the exception if an issue is encountered while processing the function app dependencies.
private Exception _initTerminatingError;
Expand Down Expand Up @@ -69,6 +74,8 @@ internal RequestProcessor(MessagingStream msgStream, System.Management.Automatio
// If an invocation is cancelled, host will receive an invocation response with status cancelled.
_requestHandlers.Add(StreamingMessage.ContentOneofCase.InvocationCancel, ProcessInvocationCancelRequest);

_requestHandlers.Add(StreamingMessage.ContentOneofCase.FunctionsMetadataRequest, ProcessFunctionMetadataRequest);

_requestHandlers.Add(StreamingMessage.ContentOneofCase.FunctionEnvironmentReloadRequest, ProcessFunctionEnvironmentReloadRequest);
}

Expand Down Expand Up @@ -134,6 +141,10 @@ internal StreamingMessage ProcessWorkerInitRequest(StreamingMessage request)
}
catch (Exception e)
{
// This is a terminating failure: we will need to return a failure response to
// all subsequent 'FunctionLoadRequest'. Cache the exception so we can reuse it in future calls.
_initTerminatingError = e;

status.Status = StatusResult.Types.Status.Failure;
status.Exception = e.ToRpcException();
return response;
Expand Down Expand Up @@ -215,21 +226,38 @@ internal StreamingMessage ProcessFunctionLoadRequest(StreamingMessage request)
var rpcLogger = new RpcLogger(_msgStream);
rpcLogger.SetContext(request.RequestId, null);

_dependencyManager = new DependencyManager(request.FunctionLoadRequest.Metadata.Directory, logger: rpcLogger);
// _functionAppRootPath is set in ProcessFunctionMetadataRequest for the v2 progamming model.
if (_functionAppRootPath == null)
{
// If _functionAppRootPath is null, this means that this is an app for the v1 programming model.
_functionAppRootPath = Path.GetFullPath(Path.Join(request.FunctionLoadRequest.Metadata.Directory, ".."));
}

if (string.IsNullOrWhiteSpace(_functionAppRootPath))
{
throw new ArgumentException("Failed to resolve the function app root", nameof(_functionAppRootPath));
}

_dependencyManager = new DependencyManager(_functionAppRootPath, logger: rpcLogger);
var managedDependenciesPath = _dependencyManager.Initialize(request, rpcLogger);

SetupAppRootPathAndModulePath(functionLoadRequest, managedDependenciesPath);
SetupAppRootPathAndModulePath(_functionAppRootPath, managedDependenciesPath);

// The profile is invoke when the instance is initialized.
// TODO: Initialize the first PowerShell instance but delay the invocation of the profile until the first function load.
// The first PowerShell instance will be used to import the AzureFunctions.PowerShell.SDK to generate the function metadata
// Issue: We do not know if Managed Dependencies is enabled until the first function load.
_powershellPool.Initialize(_firstPwshInstance);

// Start the download asynchronously if needed.
_dependencyManager.StartDependencyInstallationIfNeeded(request, _firstPwshInstance, rpcLogger);

rpcLogger.Log(isUserOnlyLog: false, LogLevel.Trace, string.Format(PowerShellWorkerStrings.FirstFunctionLoadCompleted, stopwatch.ElapsedMilliseconds));

}
catch (Exception e)
{
// Failure that happens during this step is terminating and we will need to return a failure response to
// This is a terminating failure: we will need to return a failure response to
// all subsequent 'FunctionLoadRequest'. Cache the exception so we can reuse it in future calls.
_initTerminatingError = e;

Expand Down Expand Up @@ -362,6 +390,22 @@ internal StreamingMessage ProcessInvocationCancelRequest(StreamingMessage reques
return null;
}

private StreamingMessage ProcessFunctionMetadataRequest(StreamingMessage request)
{
StreamingMessage response = NewStreamingMessageTemplate(
request.RequestId,
StreamingMessage.ContentOneofCase.FunctionMetadataResponse,
out StatusResult status);

var rpcLogger = new RpcLogger(_msgStream);
rpcLogger.SetContext(request.RequestId, null);

_functionAppRootPath = request.FunctionsMetadataRequest.FunctionAppDirectory;
response.FunctionMetadataResponse.FunctionMetadataResults.AddRange(WorkerIndexingHelper.IndexFunctions(_functionAppRootPath, rpcLogger));

return response;
}

internal StreamingMessage ProcessFunctionEnvironmentReloadRequest(StreamingMessage request)
{
var stopwatch = new Stopwatch();
Expand Down Expand Up @@ -415,6 +459,9 @@ private StreamingMessage NewStreamingMessageTemplate(string requestId, Streaming
case StreamingMessage.ContentOneofCase.FunctionEnvironmentReloadResponse:
response.FunctionEnvironmentReloadResponse = new FunctionEnvironmentReloadResponse() { Result = status };
break;
case StreamingMessage.ContentOneofCase.FunctionMetadataResponse:
response.FunctionMetadataResponse = new FunctionMetadataResponse() { Result = status };
break;
default:
throw new InvalidOperationException("Unreachable code.");
}
Expand Down Expand Up @@ -525,9 +572,9 @@ private static void BindOutputFromResult(InvocationResponse response, AzFunction
}
}

private void SetupAppRootPathAndModulePath(FunctionLoadRequest functionLoadRequest, string managedDependenciesPath)
private void SetupAppRootPathAndModulePath(string functionAppRootPath, string managedDependenciesPath)
{
FunctionLoader.SetupWellKnownPaths(functionLoadRequest, managedDependenciesPath);
FunctionLoader.SetupWellKnownPaths(functionAppRootPath, managedDependenciesPath);

if (FunctionLoader.FunctionAppRootPath == null)
{
Expand Down
59 changes: 59 additions & 0 deletions src/WorkerIndexing/BindingInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;

namespace Microsoft.Azure.Functions.PowerShellWorker.WorkerIndexing
{
internal class BindingInformation
{
private const string BindingNameKey = "name";
private const string BindingDirectionKey = "direction";
private const string BindingTypeKey = "type";
public enum Directions
{
Unknown = -1,
In = 0,
Out = 1,
Inout = 2
}

public Directions Direction { get; set; } = Directions.Unknown;
public string Type { get; set; } = "";
public string Name { get; set; } = "";
public Dictionary<string, Object> otherInformation { get; set; } = new Dictionary<string, Object>();

internal string ConvertToRpcRawBinding(out BindingInfo bindingInfo)
{
string rawBinding = string.Empty;
JObject rawBindingObject = new JObject();
rawBindingObject.Add(BindingNameKey, Name);
BindingInfo outInfo = new BindingInfo();


if (Direction == Directions.Unknown)
{
throw new Exception(string.Format(PowerShellWorkerStrings.InvalidBindingInfoDirection, Name));
}
outInfo.Direction = (BindingInfo.Types.Direction)Direction;
rawBindingObject.Add(BindingDirectionKey, Enum.GetName(typeof(BindingInfo.Types.Direction), outInfo.Direction).ToLower());
outInfo.Type = Type;
rawBindingObject.Add(BindingTypeKey, Type);

foreach (KeyValuePair<string, Object> pair in otherInformation)
{
rawBindingObject.Add(pair.Key, JToken.FromObject(pair.Value));
}

rawBinding = JsonConvert.SerializeObject(rawBindingObject);
bindingInfo = outInfo;
return rawBinding;
}
}
}
40 changes: 40 additions & 0 deletions src/WorkerIndexing/FunctionInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// 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;
using System.Collections.Generic;

namespace Microsoft.Azure.Functions.PowerShellWorker.WorkerIndexing
{
internal class FunctionInformation
{
private const string FunctionLanguagePowerShell = "powershell";

public string Directory { get; set; } = "";
public string ScriptFile { get; set; } = "";
public string Name { get; set; } = "";
public string EntryPoint { get; set; } = "";
public string FunctionId { get; set; } = "";
public List<BindingInformation> Bindings { get; set; } = new List<BindingInformation>();

internal RpcFunctionMetadata ConvertToRpc()
{
RpcFunctionMetadata returnMetadata = new RpcFunctionMetadata();
returnMetadata.FunctionId = FunctionId;
returnMetadata.Directory = Directory;
returnMetadata.EntryPoint = EntryPoint;
returnMetadata.Name = Name;
returnMetadata.ScriptFile = ScriptFile;
returnMetadata.Language = FunctionLanguagePowerShell;
foreach(BindingInformation binding in Bindings)
{
string rawBinding = binding.ConvertToRpcRawBinding(out BindingInfo bindingInfo);
returnMetadata.Bindings.Add(binding.Name, bindingInfo);
returnMetadata.RawBindings.Add(rawBinding);
}
return returnMetadata;
}
}
}
Loading