Skip to content

Commit 96131b5

Browse files
authored
making GetFunctionMetadataAsync idempotent (#10938)
1 parent 4bae424 commit 96131b5

File tree

8 files changed

+85
-10
lines changed

8 files changed

+85
-10
lines changed

release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
- Fixing invalid DateTimes in status blobs when invoking via portal (#10916)
1313
- Bug fix for platform release channel bundles resolution casing issue and additional logging (#10921)
1414
- Adding support for faas.invoke_duration metric and other spec related updates (#10929)
15+
- Fixed bug that could result in "Binding names must be unique" error (#10938)

src/WebJobs.Script.Abstractions/WebJobs.Script.Abstractions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<PackageReference Include="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" Version="2.21.0" />
1515
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
1616
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
17-
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
17+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
1818
</ItemGroup>
1919

2020
</Project>

src/WebJobs.Script.Grpc/WebJobs.Script.Grpc.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<PackageReference Include="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" Version="2.22.0" />
1717
<PackageReference Include="Microsoft.Azure.WebJobs.Rpc.Core" Version="3.0.37" />
1818
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
19-
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
19+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
2020
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
2121
<PackageReference Include="Yarp.ReverseProxy" Version="2.0.1" />
2222
</ItemGroup>

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="$(IdentityDependencyVersion)" />
7474
<PackageReference Include="Microsoft.Security.Utilities" Version="1.3.0" />
7575
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
76-
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
76+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
7777
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="$(IdentityDependencyVersion)" />
7878

7979
<!--

src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ internal FunctionMetadata ValidateBindings(IEnumerable<string> rawBindings, Func
251251
{
252252
HashSet<string> bindingNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
253253

254+
// This method takes the RawBindings and adds them to the FunctionMetadata object. It's possible
255+
// to call this twice, and we don't want to duplicate the bindings in that case.
256+
function.Bindings.Clear();
257+
254258
foreach (string binding in rawBindings)
255259
{
256260
var deserializedObj = JsonConvert.DeserializeObject<JObject>(binding, _dateTimeSerializerSettings);

src/WebJobs.Script/WebJobs.Script.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.0" />
5757
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
5858
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
59-
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
59+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
6060
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.0" />
6161
<PackageReference Include="System.Drawing.Common" Version="8.0.0" />
6262
<PackageReference Include="System.Formats.Asn1" Version="6.0.1" />

test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<PackageReference Include="Microsoft.Azure.Functions.PythonWorker" Version="4.35.0" />
3434
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
3535
<PackageReference Include="Moq" Version="4.18.4" />
36-
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
36+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
3737
<PrivateAssets>all</PrivateAssets>
3838
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3939
</PackageReference>

test/WebJobs.Script.Tests/WorkerFunctionMetadataProviderTests.cs

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.Azure.WebJobs.Script.Description;
1111
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
1212
using Microsoft.Extensions.Logging;
13+
using Microsoft.Extensions.Logging.Abstractions;
1314
using Microsoft.Extensions.Options;
1415
using Moq;
1516
using Xunit;
@@ -29,11 +30,11 @@ public WorkerFunctionMetadataProviderTests()
2930
var mockScriptHostManager = new Mock<IScriptHostManager>();
3031

3132
_workerFunctionMetadataProvider = new WorkerFunctionMetadataProvider(
32-
mockScriptOptions.Object,
33-
mockLogger.Object,
34-
mockEnvironment.Object,
35-
mockChannelManager.Object,
36-
mockScriptHostManager.Object);
33+
mockScriptOptions.Object,
34+
mockLogger.Object,
35+
mockEnvironment.Object,
36+
mockChannelManager.Object,
37+
mockScriptHostManager.Object);
3738
}
3839

3940
[Fact]
@@ -221,5 +222,74 @@ public void ValidateFunctionMetadata_IsoStringNotAltered()
221222
var function = _workerFunctionMetadataProvider.ValidateBindings(rawBindings, functionMetadata);
222223
Assert.Equal(isoString, function.Bindings.FirstOrDefault().Raw["startFromTime"].ToString());
223224
}
225+
226+
[Fact]
227+
public async Task GetFunctionMetadataAsync_Idempotent()
228+
{
229+
var mockFunctionMetadataProvider = new Mock<IWorkerFunctionMetadataProvider>(MockBehavior.Strict);
230+
var mockChannelManager = new Mock<IWebHostRpcWorkerChannelManager>(MockBehavior.Strict);
231+
var mockScriptHostManager = new Mock<IScriptHostManager>(MockBehavior.Strict);
232+
var mockOptionsMonitor = new Mock<IOptionsMonitor<ScriptApplicationHostOptions>>(MockBehavior.Strict);
233+
var scriptOptions = new ScriptApplicationHostOptions
234+
{
235+
IsFileSystemReadOnly = true
236+
};
237+
mockOptionsMonitor.Setup(m => m.CurrentValue).Returns(scriptOptions);
238+
239+
var testEnvironment = new TestEnvironment();
240+
testEnvironment.SetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "node");
241+
242+
var mockRpcWorkerChannel = new Mock<IRpcWorkerChannel>(MockBehavior.Strict);
243+
var rawFunctionMetadataList = new List<RawFunctionMetadata>
244+
{
245+
new RawFunctionMetadata
246+
{
247+
Metadata = new FunctionMetadata { Name = "TestFunction" },
248+
Bindings = ["{\"type\": \"httpTrigger\", \"name\": \"req\", \"direction\": \"in\"}"],
249+
UseDefaultMetadataIndexing = false
250+
}
251+
};
252+
mockRpcWorkerChannel.Setup(m => m.GetFunctionMetadata()).ReturnsAsync(rawFunctionMetadataList);
253+
254+
var tcs = new TaskCompletionSource<IRpcWorkerChannel>();
255+
tcs.SetResult(mockRpcWorkerChannel.Object);
256+
var channels = new Dictionary<string, TaskCompletionSource<IRpcWorkerChannel>>
257+
{
258+
{ "testWorkerId", tcs }
259+
};
260+
261+
mockChannelManager.Setup(m => m.GetChannels("node")).Returns(channels);
262+
263+
var provider = new WorkerFunctionMetadataProvider(
264+
mockOptionsMonitor.Object,
265+
NullLogger<WorkerFunctionMetadataProvider>.Instance,
266+
testEnvironment,
267+
mockChannelManager.Object,
268+
mockScriptHostManager.Object);
269+
270+
var workerConfigs = new List<RpcWorkerConfig>();
271+
272+
// Calling this twice should return the same data
273+
var result1 = await provider.GetFunctionMetadataAsync(workerConfigs, true);
274+
var result2 = await provider.GetFunctionMetadataAsync(workerConfigs, true);
275+
276+
var function1 = result1.Functions.Single();
277+
var function2 = result2.Functions.Single();
278+
279+
static void AssertFunction(FunctionMetadata function)
280+
{
281+
Assert.Equal("TestFunction", function.Name);
282+
Assert.Equal("node", function.Language);
283+
Assert.Collection(function.Bindings, binding =>
284+
{
285+
Assert.Equal("httpTrigger", binding.Type);
286+
Assert.Equal("req", binding.Name);
287+
Assert.Equal(BindingDirection.In, binding.Direction);
288+
});
289+
}
290+
291+
AssertFunction(function1);
292+
AssertFunction(function2);
293+
}
224294
}
225295
}

0 commit comments

Comments
 (0)