Skip to content

Commit 6ebfee4

Browse files
authored
Apply capabilities correctly on reload (#9367)
1 parent 33957ea commit 6ebfee4

File tree

16 files changed

+250
-76
lines changed

16 files changed

+250
-76
lines changed

benchmarks/WebJobs.Script.Benchmarks/GrpcMessageConversionBenchmarks.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void Setup()
6565
{
6666
{ RpcWorkerConstants.TypedDataCollection, "1" }
6767
};
68-
grpcCapabilities.UpdateCapabilities(addedCapabilities);
68+
grpcCapabilities.UpdateCapabilities(addedCapabilities, GrpcCapabilitiesUpdateStrategy.Merge);
6969
}
7070
}
7171
}

release_notes.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
- Update PowerShell Worker 7.0 to 4.0.2850 [Release Note](https://github.com/Azure/azure-functions-powershell-worker/releases/tag/v4.0.2850)
99
- Update Node.js Worker Version to [3.8.0](https://github.com/Azure/azure-functions-nodejs-worker/releases/tag/v3.8.0)
1010
- Identity dependencies updated to 6.31.0:
11-
- Microsoft.IdentityModel.Tokens
12-
- System.IdentityModel.Tokens.Jwt
13-
- Microsoft.IdentityModel.Abstractions
14-
- Microsoft.IdentityModel.JsonWebTokens
15-
- Microsoft.IdentityModel.Logging
11+
- Microsoft.IdentityModel.Tokens
12+
- System.IdentityModel.Tokens.Jwt
13+
- Microsoft.IdentityModel.Abstractions
14+
- Microsoft.IdentityModel.JsonWebTokens
15+
- Microsoft.IdentityModel.Logging
1616
- Updated Grpc.AspNetCore package to 2.55.0 (https://github.com/Azure/azure-functions-host/pull/9373)
1717
- Update protobuf file to v1.10.0 (https://github.com/Azure/azure-functions-host/pull/9405)
1818
- Send an empty RpcHttp payload if proxying the http request to the worker (https://github.com/Azure/azure-functions-host/pull/9415)
1919
- Add new Host to Worker RPC extensibility feature for out-of-proc workers. (https://github.com/Azure/azure-functions-host/pull/9292)
20+
- Apply capabilities on environment reload (placeholder mode scenarios) (https://github.com/Azure/azure-functions-host/pull/9367)

src/WebJobs.Script.Grpc/Channel/GrpcCapabilities.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Text.Json;
67
using Microsoft.Extensions.Logging;
78

89
namespace Microsoft.Azure.WebJobs.Script.Grpc
@@ -26,19 +27,36 @@ public string GetCapabilityState(string capability)
2627
return null;
2728
}
2829

29-
public void UpdateCapabilities(IDictionary<string, string> capabilities)
30+
public void UpdateCapabilities(IDictionary<string, string> capabilities, GrpcCapabilitiesUpdateStrategy strategy)
3031
{
3132
if (capabilities == null)
3233
{
3334
return;
3435
}
3536

36-
_logger.LogDebug($"Updating capabilities: {capabilities.ToString()}");
37+
_logger.LogDebug("Updating capabilities using {strategy} strategy. Current: {_capabilities} Incoming: {capabilities}", strategy, JsonSerializer.Serialize(_capabilities), JsonSerializer.Serialize(capabilities));
3738

38-
foreach (KeyValuePair<string, string> capability in capabilities)
39+
switch (strategy)
3940
{
40-
UpdateCapability(capability);
41+
case GrpcCapabilitiesUpdateStrategy.Merge:
42+
foreach (KeyValuePair<string, string> capability in capabilities)
43+
{
44+
UpdateCapability(capability);
45+
}
46+
break;
47+
48+
case GrpcCapabilitiesUpdateStrategy.Replace:
49+
_capabilities.Clear();
50+
foreach (KeyValuePair<string, string> capability in capabilities)
51+
{
52+
UpdateCapability(capability);
53+
}
54+
break;
55+
default:
56+
throw new InvalidOperationException($"Did not recognize the capability update strategy {strategy}.");
4157
}
58+
59+
_logger.LogDebug("Updated capabilities: {capabilities}", JsonSerializer.Serialize(_capabilities));
4260
}
4361

4462
private void UpdateCapability(KeyValuePair<string, string> capability)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Azure.WebJobs.Script.Grpc
5+
{
6+
internal enum GrpcCapabilitiesUpdateStrategy
7+
{
8+
// overwrites existing values and appends new ones
9+
// ex. worker init: {A: foo, B: bar} + env reload: {A:foo, B: foo, C: foo} -> {A: foo, B: foo, C: foo}
10+
Merge,
11+
// existing capabilities are cleared and new capabilities are applied
12+
// ex. worker init: {A: foo, B: bar} + env reload: {A:foo, C: foo} -> {A: foo, C: foo}
13+
Replace
14+
}
15+
}

src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,7 @@ internal void FunctionEnvironmentReloadResponse(FunctionEnvironmentReloadRespons
372372
_workerConfig.Description.DefaultRuntimeVersion = _workerConfig.Description.DefaultRuntimeVersion ?? res?.WorkerMetadata?.RuntimeVersion;
373373
_workerConfig.Description.DefaultRuntimeName = _workerConfig.Description.DefaultRuntimeName ?? res?.WorkerMetadata?.RuntimeName;
374374

375-
UpdateCapabilities(res.Capabilities);
376-
_cancelCapabilityEnabled ??= !string.IsNullOrEmpty(_workerCapabilities.GetCapabilityState(RpcWorkerConstants.HandlesInvocationCancelMessage));
375+
ApplyCapabilities(res.Capabilities, res.CapabilitiesUpdateStrategy.ToGrpcCapabilitiesUpdateStrategy());
377376

378377
if (res.Result.IsFailure(out Exception reloadEnvironmentVariablesException))
379378
{
@@ -410,7 +409,36 @@ internal void WorkerInitResponse(GrpcEvent initEvent)
410409

411410
_state = _state | RpcWorkerChannelState.Initialized;
412411

413-
UpdateCapabilities(_initMessage.Capabilities);
412+
ApplyCapabilities(_initMessage.Capabilities);
413+
414+
_workerInitTask.TrySetResult(true);
415+
}
416+
417+
private void LogWorkerMetadata(WorkerMetadata workerMetadata)
418+
{
419+
if (workerMetadata == null)
420+
{
421+
return;
422+
}
423+
424+
workerMetadata.UpdateWorkerMetadata(_workerConfig);
425+
var workerMetadataString = workerMetadata.ToString();
426+
_metricsLogger.LogEvent(MetricEventNames.WorkerMetadata, functionName: null, workerMetadataString);
427+
_workerChannelLogger.LogDebug("Worker metadata: {workerMetadata}", workerMetadataString);
428+
}
429+
430+
// Allow tests to add capabilities, even if not directly supported by the worker.
431+
internal virtual void UpdateCapabilities(IDictionary<string, string> fields, GrpcCapabilitiesUpdateStrategy strategy)
432+
{
433+
_workerCapabilities.UpdateCapabilities(fields, strategy);
434+
}
435+
436+
// Helper method that updates and applies capabilities
437+
// Used at worker initialization and environment reload (placeholder scenarios)
438+
// The default strategy for updating capabilities is merge
439+
internal void ApplyCapabilities(IDictionary<string, string> capabilities, GrpcCapabilitiesUpdateStrategy strategy = GrpcCapabilitiesUpdateStrategy.Merge)
440+
{
441+
UpdateCapabilities(capabilities, strategy);
414442

415443
_isSharedMemoryDataTransferEnabled = IsSharedMemoryDataTransferEnabled();
416444
_cancelCapabilityEnabled ??= !string.IsNullOrEmpty(_workerCapabilities.GetCapabilityState(RpcWorkerConstants.HandlesInvocationCancelMessage));
@@ -441,27 +469,6 @@ internal void WorkerInitResponse(GrpcEvent initEvent)
441469
HandleWorkerInitError(ex);
442470
}
443471
}
444-
445-
_workerInitTask.TrySetResult(true);
446-
}
447-
448-
private void LogWorkerMetadata(WorkerMetadata workerMetadata)
449-
{
450-
if (workerMetadata == null)
451-
{
452-
return;
453-
}
454-
455-
workerMetadata.UpdateWorkerMetadata(_workerConfig);
456-
var workerMetadataString = workerMetadata.ToString();
457-
_metricsLogger.LogEvent(MetricEventNames.WorkerMetadata, functionName: null, workerMetadataString);
458-
_workerChannelLogger.LogDebug("Worker metadata: {workerMetadata}", workerMetadataString);
459-
}
460-
461-
// Allow tests to add capabilities, even if not directly supported by the worker.
462-
internal virtual void UpdateCapabilities(IDictionary<string, string> fields)
463-
{
464-
_workerCapabilities.UpdateCapabilities(fields);
465472
}
466473

467474
public void SetupFunctionInvocationBuffers(IEnumerable<FunctionMetadata> functions)

src/WebJobs.Script.Grpc/MessageExtensions/GrpcMessageConversionExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,14 @@ internal static RetryStrategy ToRetryStrategy(this RpcRetryOptions.Types.RetrySt
371371
_ => throw new InvalidOperationException($"Unknown RetryStrategy RpcDataType: {retryStrategy}.")
372372
};
373373

374+
internal static GrpcCapabilitiesUpdateStrategy ToGrpcCapabilitiesUpdateStrategy(this FunctionEnvironmentReloadResponse.Types.CapabilitiesUpdateStrategy capabilityUpdateStrategy) =>
375+
capabilityUpdateStrategy switch
376+
{
377+
FunctionEnvironmentReloadResponse.Types.CapabilitiesUpdateStrategy.Merge => GrpcCapabilitiesUpdateStrategy.Merge,
378+
FunctionEnvironmentReloadResponse.Types.CapabilitiesUpdateStrategy.Replace => GrpcCapabilitiesUpdateStrategy.Replace,
379+
_ => throw new InvalidOperationException($"Unknown capabilities update strategy: {capabilityUpdateStrategy}.")
380+
};
381+
374382
private static bool ShouldIncludeEmptyEntriesInMessagePayload(GrpcCapabilities capabilities)
375383
{
376384
return !string.IsNullOrWhiteSpace(capabilities.GetCapabilityState(RpcWorkerConstants.IncludeEmptyEntriesInMessagePayload));

test/DotNetIsolated60/DotNetIsolated60.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
<FunctionsEnableWorkerIndexing>True</FunctionsEnableWorkerIndexing>
99
</PropertyGroup>
1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.14.0" />
11+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.18.0" />
1212
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
13-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="4.0.4" />
14-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.10.0" />
13+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.0.0-preview2" />
14+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="6.0.0" />
15+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.12.0" />
1516
</ItemGroup>
1617
<ItemGroup>
1718
<None Update="host.json">

test/DotNetIsolated60/Function1.cs renamed to test/DotNetIsolated60/HttpRequestDataFunction.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55

66
namespace DotNetIsolated60
77
{
8-
public class Function1
8+
public class HttpRequestDataFunction
99
{
10-
private readonly ILogger _logger;
10+
private readonly ILogger<HttpRequestDataFunction> _logger;
1111

12-
public Function1(ILoggerFactory loggerFactory)
12+
public HttpRequestDataFunction(ILogger<HttpRequestDataFunction> logger)
1313
{
14-
_logger = loggerFactory.CreateLogger<Function1>();
14+
_logger = logger;
1515
}
1616

17-
[Function("Function1")]
17+
[Function(nameof(HttpRequestDataFunction))]
1818
public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
1919
{
2020
_logger.LogInformation("C# HTTP trigger function processed a request.");
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.Azure.Functions.Worker;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace DotNetIsolated60
6+
{
7+
public class HttpRequestFunction
8+
{
9+
private readonly ILogger<HttpRequestFunction> _logger;
10+
11+
public HttpRequestFunction(ILogger<HttpRequestFunction> logger)
12+
{
13+
_logger = logger;
14+
}
15+
16+
[Function(nameof(HttpRequestFunction))]
17+
public Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
18+
{
19+
_logger.LogInformation("C# HTTP trigger function processed a request.");
20+
return req.HttpContext.Response.WriteAsync("Welcome to Azure Functions!");
21+
}
22+
}
23+
}

test/DotNetIsolated60/Program.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,23 @@
33

44
//Debugger.Launch();
55

6-
var host = new HostBuilder()
7-
.ConfigureFunctionsWorkerDefaults()
8-
.ConfigureGeneratedFunctionMetadataProvider()
9-
.Build();
6+
// Tests can set an env var that will swap this to use the proxy
7+
bool useProxy = Environment.GetEnvironmentVariable("UseProxyInTest")?.Contains("1") ?? false;
108

9+
var hostBuilder = new HostBuilder();
10+
11+
if (useProxy)
12+
{
13+
hostBuilder
14+
.ConfigureFunctionsWebApplication()
15+
.ConfigureGeneratedFunctionMetadataProvider();
16+
}
17+
else
18+
{
19+
hostBuilder
20+
.ConfigureFunctionsWorkerDefaults()
21+
.ConfigureGeneratedFunctionMetadataProvider();
22+
}
23+
24+
var host = hostBuilder.Build();
1125
host.Run();

0 commit comments

Comments
 (0)