diff --git a/host/src/FunctionsNetHost/Grpc/Exceptions/EnvironmentReloadNotSupportedException.cs b/host/src/FunctionsNetHost/Grpc/Exceptions/EnvironmentReloadNotSupportedException.cs new file mode 100644 index 000000000..a0996236e --- /dev/null +++ b/host/src/FunctionsNetHost/Grpc/Exceptions/EnvironmentReloadNotSupportedException.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// The exception that is thrown when the current function app payload does not support environment reload. + /// + public sealed class EnvironmentReloadNotSupportedException : NotSupportedException + { + public EnvironmentReloadNotSupportedException() { } + + public EnvironmentReloadNotSupportedException(string message) : base(message) { } + } +} diff --git a/host/src/FunctionsNetHost/Grpc/Exceptions/FunctionAppPayloadNotFoundException.cs b/host/src/FunctionsNetHost/Grpc/Exceptions/FunctionAppPayloadNotFoundException.cs new file mode 100644 index 000000000..97bfac80a --- /dev/null +++ b/host/src/FunctionsNetHost/Grpc/Exceptions/FunctionAppPayloadNotFoundException.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.Functions.Worker +{ + /// + /// The exception that is thrown when there is no function app payload found. + /// + public sealed class FunctionAppPayloadNotFoundException : Exception + { + public FunctionAppPayloadNotFoundException() { } + + public FunctionAppPayloadNotFoundException(string message) : base(message) { } + } +} diff --git a/host/src/FunctionsNetHost/Grpc/IncomingGrpcMessageHandler.cs b/host/src/FunctionsNetHost/Grpc/IncomingGrpcMessageHandler.cs index bac4f5f42..af0a5c43f 100644 --- a/host/src/FunctionsNetHost/Grpc/IncomingGrpcMessageHandler.cs +++ b/host/src/FunctionsNetHost/Grpc/IncomingGrpcMessageHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Grpc.Messages; namespace FunctionsNetHost.Grpc @@ -50,16 +51,26 @@ private async Task Process(StreamingMessage msg) Logger.LogTrace("Specialization request received."); var envReloadRequest = msg.FunctionEnvironmentReloadRequest; - var applicationExePath = PathUtils.GetApplicationExePath(envReloadRequest.FunctionAppDirectory); - Logger.LogTrace($"application path {applicationExePath}"); - if (string.IsNullOrWhiteSpace(applicationExePath)) + var workerConfig = await WorkerConfigUtils.GetWorkerConfig(envReloadRequest.FunctionAppDirectory); + + if (workerConfig?.Description is null) + { + responseMessage.FunctionEnvironmentReloadResponse = BuildFailedEnvironmentReloadResponse(new FunctionAppPayloadNotFoundException()); + break; + } + + // function app payload which uses an older version of Microsoft.Azure.Functions.Worker package does not support specialization. + if (!workerConfig.Description.CanUsePlaceholder) { - var ex = new InvalidOperationException($"Unable to find a valid function app payload at '{envReloadRequest.FunctionAppDirectory}'"); - responseMessage.FunctionEnvironmentReloadResponse = BuildFailedEnvironmentReloadResponse(ex); + Logger.LogTrace("App payload uses an older version of worker package which does not support specialization."); + responseMessage.FunctionEnvironmentReloadResponse = BuildFailedEnvironmentReloadResponse(new EnvironmentReloadNotSupportedException()); break; } + var applicationExePath = Path.Combine(envReloadRequest.FunctionAppDirectory, workerConfig.Description.DefaultWorkerPath!); + Logger.LogTrace($"application path {applicationExePath}"); + foreach (var kv in envReloadRequest.EnvironmentVariables) { EnvironmentUtils.SetValue(kv.Key, kv.Value); diff --git a/host/src/FunctionsNetHost/Grpc/PathUtils.cs b/host/src/FunctionsNetHost/Grpc/PathUtils.cs deleted file mode 100644 index 839d79d0d..000000000 --- a/host/src/FunctionsNetHost/Grpc/PathUtils.cs +++ /dev/null @@ -1,53 +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.Text.Json; -using System.Text.Json.Nodes; - -namespace FunctionsNetHost.Grpc -{ - internal static class PathUtils - { - /// - /// Gets the absolute path to worker application executable. - /// Builds the path by reading the worker.config.json - /// - /// The FunctionAppDirectory value from environment reload request. - internal static string? GetApplicationExePath(string applicationDirectory) - { - string jsonString = string.Empty; - string workerConfigPath = string.Empty; - try - { - workerConfigPath = Path.Combine(applicationDirectory, "worker.config.json"); - - jsonString = File.ReadAllText(workerConfigPath); - var workerConfigJsonNode = JsonNode.Parse(jsonString)!; - var executableName = workerConfigJsonNode["description"]?["defaultWorkerPath"]?.ToString(); - - if (executableName == null) - { - Logger.Log($"Invalid worker configuration. description > defaultWorkerPath property value is null. jsonString:{jsonString}"); - return null; - } - - return Path.Combine(applicationDirectory, executableName); - } - catch (FileNotFoundException ex) - { - Logger.Log($"{workerConfigPath} file not found.{ex}"); - return null; - } - catch (JsonException ex) - { - Logger.Log($"Error parsing JSON in GetApplicationExePath.{ex}. jsonString:{jsonString}"); - return null; - } - catch (Exception ex) - { - Logger.Log($"Error in GetApplicationExePath.{ex}. jsonString:{jsonString}"); - return null; - } - } - } -} diff --git a/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfig.cs b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfig.cs new file mode 100644 index 000000000..b15b46e3f --- /dev/null +++ b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfig.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace FunctionsNetHost.Grpc +{ + /// + /// Represents a worker configuration instance. + /// + public sealed class WorkerConfig + { + public WorkerDescription? Description { set; get; } + } +} diff --git a/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfigSerializerContext.cs b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfigSerializerContext.cs new file mode 100644 index 000000000..d37bcd11e --- /dev/null +++ b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfigSerializerContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Text.Json.Serialization; + +namespace FunctionsNetHost.Grpc +{ + [JsonSerializable(typeof(WorkerConfig))] + [JsonSourceGenerationOptions(IncludeFields = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + internal partial class WorkerConfigSerializerContext : JsonSerializerContext { } +} diff --git a/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfigUtils.cs b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfigUtils.cs new file mode 100644 index 000000000..98c430718 --- /dev/null +++ b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerConfigUtils.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Text.Json; + +namespace FunctionsNetHost.Grpc +{ + internal static class WorkerConfigUtils + { + /// + /// Builds and returns an instance of from the worker.config.json file if present in the application directory. + /// + /// The directory where function app deployed payload is present. + internal static async Task GetWorkerConfig(string applicationDirectory) + { + string workerConfigPath = string.Empty; + + try + { + workerConfigPath = Path.Combine(applicationDirectory, "worker.config.json"); + + using Stream stream = File.OpenRead(workerConfigPath); + var workerConfig = await JsonSerializer.DeserializeAsync(stream, WorkerConfigSerializerContext.Default.WorkerConfig); + + return workerConfig; + } + catch (FileNotFoundException) + { + Logger.Log($"worker.config.json not found at {workerConfigPath}. This may indicate missing app payload."); + return null; + } + catch (Exception ex) + { + Logger.Log($"Error in WorkerConfigUtils.GetWorkerConfig.{ex}"); + return null; + } + } + } +} diff --git a/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerDescription.cs b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerDescription.cs new file mode 100644 index 000000000..5ad21dd0a --- /dev/null +++ b/host/src/FunctionsNetHost/Grpc/WorkerConfig/WorkerDescription.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace FunctionsNetHost.Grpc +{ + public sealed class WorkerDescription + { + public string? DefaultWorkerPath { set; get; } + + public bool CanUsePlaceholder { set; get; } + } +} diff --git a/host/src/FunctionsNetHost/global.json b/host/src/FunctionsNetHost/global.json index f735d6ed6..27928313f 100644 --- a/host/src/FunctionsNetHost/global.json +++ b/host/src/FunctionsNetHost/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100-rc.1.23455.8", + "version": "8.0.100-rc.2.23502.2", "allowPrerelease": true, "rollForward": "latestMinor" } diff --git a/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec b/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec index c2ab2b44b..36c3591a6 100644 --- a/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec +++ b/host/tools/build/Microsoft.Azure.Functions.DotnetIsolatedNativeHost.nuspec @@ -4,7 +4,7 @@ Microsoft.Azure.Functions.DotNetIsolatedNativeHost Microsoft Azure Functions dotnet-isolated native host dotnet-isolated azure-functions azure - 1.0.0 + 1.0.1 Microsoft Microsoft https://github.com/Azure/azure-functions-dotnet-worker