Skip to content

Commit 1535420

Browse files
Worker Indexing - Adding log to inform about mixed function app (#8201)
* Adding logic to detect mixed app and log it * Added tests * Test logger string * added tests * Tests * Tests refactoring * passing scripthostoptions * Taking scriptpath from scriptJobHostoptions * Added list of legacy functions
1 parent 3308d07 commit 1535420

File tree

3 files changed

+105
-10
lines changed

3 files changed

+105
-10
lines changed

src/WebJobs.Script/Host/AggregateFunctionMetadataProvider.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
7+
using System.IO.Abstractions;
78
using System.Linq;
89
using System.Reactive.Linq;
910
using System.Runtime.CompilerServices;
@@ -14,6 +15,7 @@
1415
using Microsoft.Azure.WebJobs.Script.Workers;
1516
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
1617
using Microsoft.Extensions.Logging;
18+
using Microsoft.Extensions.Options;
1719
using Newtonsoft.Json.Linq;
1820

1921
namespace Microsoft.Azure.WebJobs.Script
@@ -24,16 +26,19 @@ public class AggregateFunctionMetadataProvider : IFunctionMetadataProvider
2426
private readonly ILogger _logger;
2527
private readonly IFunctionInvocationDispatcher _dispatcher;
2628
private ImmutableArray<FunctionMetadata> _functions;
29+
private IOptions<ScriptJobHostOptions> _scriptOptions;
2730
private IFunctionMetadataProvider _hostFunctionMetadataProvider;
2831

2932
public AggregateFunctionMetadataProvider(
3033
ILogger logger,
3134
IFunctionInvocationDispatcher invocationDispatcher,
32-
IFunctionMetadataProvider hostFunctionMetadataProvider)
35+
IFunctionMetadataProvider hostFunctionMetadataProvider,
36+
IOptions<ScriptJobHostOptions> scriptOptions)
3337
{
3438
_logger = logger;
3539
_dispatcher = invocationDispatcher;
3640
_hostFunctionMetadataProvider = hostFunctionMetadataProvider;
41+
_scriptOptions = scriptOptions;
3742
}
3843

3944
public ImmutableDictionary<string, ImmutableArray<string>> FunctionErrors
@@ -74,6 +79,9 @@ public async Task<ImmutableArray<FunctionMetadata>> GetFunctionMetadataAsync(IEn
7479

7580
// set up invocation buffers and send load requests
7681
await _dispatcher.FinishInitialization(functions);
82+
83+
// Validate if the app has functions in legacy format and add in logs to inform about the mixed app
84+
_ = Task.Delay(TimeSpan.FromMinutes(1)).ContinueWith(t => ValidateFunctionAppFormat(_scriptOptions.Value.RootScriptPath, _logger));
7785
}
7886
else
7987
{
@@ -85,6 +93,32 @@ public async Task<ImmutableArray<FunctionMetadata>> GetFunctionMetadataAsync(IEn
8593
return _functions;
8694
}
8795

96+
internal static void ValidateFunctionAppFormat(string scriptPath, ILogger logger, IFileSystem fileSystem = null)
97+
{
98+
fileSystem = fileSystem ?? FileUtility.Instance;
99+
bool mixedApp = false;
100+
string legacyFormatFunctions = null;
101+
102+
if (fileSystem.Directory.Exists(scriptPath))
103+
{
104+
var functionDirectories = fileSystem.Directory.EnumerateDirectories(scriptPath).ToImmutableArray();
105+
foreach (var functionDirectory in functionDirectories)
106+
{
107+
if (Utility.TryReadFunctionConfig(functionDirectory, out string json, fileSystem))
108+
{
109+
mixedApp = true;
110+
var functionName = functionDirectory.Split('\\').Last();
111+
legacyFormatFunctions = legacyFormatFunctions != null ? legacyFormatFunctions + ", " + functionName : functionName;
112+
}
113+
}
114+
115+
if (mixedApp)
116+
{
117+
logger.Log(LogLevel.Information, $"Detected mixed function app. Some functions may not be indexed - {legacyFormatFunctions}");
118+
}
119+
}
120+
}
121+
88122
internal IEnumerable<FunctionMetadata> ValidateMetadata(IEnumerable<RawFunctionMetadata> functions)
89123
{
90124
List<FunctionMetadata> validatedMetadata = new List<FunctionMetadata>();

src/WebJobs.Script/Host/FunctionMetadataManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ public class FunctionMetadataManager : IFunctionMetadataManager
3636
private ConcurrentDictionary<string, FunctionMetadata> _functionMetadataMap = new ConcurrentDictionary<string, FunctionMetadata>(StringComparer.OrdinalIgnoreCase);
3737

3838
public FunctionMetadataManager(IOptions<ScriptJobHostOptions> scriptOptions, IFunctionMetadataProvider functionMetadataProvider,
39-
IOptions<HttpWorkerOptions> httpWorkerOptions, IScriptHostManager scriptHostManager, ILoggerFactory loggerFactory, IOptions<LanguageWorkerOptions> languageWorkerOptions, IEnvironment environment)
39+
IOptions<HttpWorkerOptions> httpWorkerOptions, IScriptHostManager scriptHostManager, ILoggerFactory loggerFactory,
40+
IOptions<LanguageWorkerOptions> languageWorkerOptions, IEnvironment environment)
4041
{
4142
_scriptOptions = scriptOptions;
4243
_languageWorkerOptions = languageWorkerOptions;
4344
_serviceProvider = scriptHostManager as IServiceProvider;
4445
_functionMetadataProvider = functionMetadataProvider;
45-
4646
_loggerFactory = loggerFactory;
4747
_logger = loggerFactory.CreateLogger(LogCategories.Startup);
4848
_isHttpWorker = httpWorkerOptions?.Value?.Description != null;
@@ -132,7 +132,7 @@ internal ImmutableArray<FunctionMetadata> LoadFunctionMetadata(bool forceRefresh
132132
ImmutableArray<FunctionMetadata> immutableFunctionMetadata;
133133
var workerConfigs = _languageWorkerOptions.Value.WorkerConfigs;
134134

135-
IFunctionMetadataProvider metadataProvider = new AggregateFunctionMetadataProvider(_loggerFactory.CreateLogger<AggregateFunctionMetadataProvider>(), dispatcher, _functionMetadataProvider);
135+
IFunctionMetadataProvider metadataProvider = new AggregateFunctionMetadataProvider(_loggerFactory.CreateLogger<AggregateFunctionMetadataProvider>(), dispatcher, _functionMetadataProvider, _scriptOptions);
136136

137137
immutableFunctionMetadata = metadataProvider.GetFunctionMetadataAsync(workerConfigs, SystemEnvironment.Instance, forceRefresh).GetAwaiter().GetResult();
138138

test/WebJobs.Script.Tests/WorkerFunctionMetadataProviderTests.cs renamed to test/WebJobs.Script.Tests/AggregateFunctionMetadataProviderTests.cs

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,28 @@
1010
using System.Threading.Tasks;
1111
using Microsoft.Azure.WebJobs.Script.Description;
1212
using Microsoft.Azure.WebJobs.Script.Diagnostics;
13+
using Microsoft.Azure.WebJobs.Script.Tests.Workers.Rpc;
1314
using Microsoft.Azure.WebJobs.Script.Workers;
1415
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
1516
using Microsoft.Extensions.Logging;
1617
using Microsoft.Extensions.Logging.Abstractions;
18+
using Microsoft.Extensions.Options;
19+
using Microsoft.WebJobs.Script.Tests;
1720
using Moq;
1821
using Xunit;
1922

2023
namespace Microsoft.Azure.WebJobs.Script.Tests
2124
{
22-
public class WorkerFunctionMetadataProviderTests
25+
public class AggregateFunctionMetadataProviderTests
2326
{
2427
private readonly TestLogger _logger;
2528
private AggregateFunctionMetadataProvider _aggregateFunctionMetadataProvider;
2629
private Mock<IFunctionInvocationDispatcher> _mockRpcFunctionInvocationDispatcher;
2730
private Mock<IFunctionMetadataProvider> _mockFunctionMetadataProvider;
2831

29-
public WorkerFunctionMetadataProviderTests()
32+
public AggregateFunctionMetadataProviderTests()
3033
{
31-
_logger = new TestLogger("WorkerFunctionMetadataProviderTests");
34+
_logger = new TestLogger("AggregateFunctionMetadataProviderTests");
3235
_mockRpcFunctionInvocationDispatcher = new Mock<IFunctionInvocationDispatcher>();
3336
_mockFunctionMetadataProvider = new Mock<IFunctionMetadataProvider>();
3437
}
@@ -158,6 +161,8 @@ public void GetFunctionMetadataAsync_WorkerIndexing_HostFallback()
158161

159162
var workerConfigs = TestHelpers.GetTestWorkerConfigs().ToImmutableArray();
160163
workerConfigs.ToList().ForEach(config => config.Description.WorkerIndexing = "true");
164+
var scriptjobhostoptions = new ScriptJobHostOptions();
165+
scriptjobhostoptions.RootScriptPath = Path.Combine(Environment.CurrentDirectory, @"..", "..", "..", "..", "..", "sample", "node");
161166

162167
var environment = SystemEnvironment.Instance;
163168
environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime, "node");
@@ -168,7 +173,7 @@ public void GetFunctionMetadataAsync_WorkerIndexing_HostFallback()
168173
_mockRpcFunctionInvocationDispatcher.Setup(m => m.FinishInitialization(functionMetadataCollection, default)).Returns(Task.FromResult(0));
169174
_mockFunctionMetadataProvider.Setup(m => m.GetFunctionMetadataAsync(workerConfigs, environment, false)).Returns(Task.FromResult(functionMetadataCollection.ToImmutableArray()));
170175

171-
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(_logger, _mockRpcFunctionInvocationDispatcher.Object, _mockFunctionMetadataProvider.Object);
176+
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(_logger, _mockRpcFunctionInvocationDispatcher.Object, _mockFunctionMetadataProvider.Object, new OptionsWrapper<ScriptJobHostOptions>(scriptjobhostoptions));
172177

173178
// Act
174179
var functions = _aggregateFunctionMetadataProvider.GetFunctionMetadataAsync(workerConfigs, environment, false).GetAwaiter().GetResult();
@@ -193,13 +198,15 @@ public void GetFunctionMetadataAsync_HostIndexing()
193198
var workerConfigs = TestHelpers.GetTestWorkerConfigs().ToImmutableArray();
194199
var environment = SystemEnvironment.Instance;
195200
environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime, "node");
201+
var scriptjobhostoptions = new ScriptJobHostOptions();
202+
scriptjobhostoptions.RootScriptPath = Path.Combine(Environment.CurrentDirectory, @"..", "..", "..", "..", "..", "sample", "node");
196203

197204
_mockRpcFunctionInvocationDispatcher.Setup(m => m.InitializeAsync(functionMetadataCollection, default)).Returns(Task.FromResult(0));
198205
_mockRpcFunctionInvocationDispatcher.Setup(m => m.GetWorkerMetadata()).Returns(Task.FromResult(rawFunctionMetadataCollection));
199206
_mockRpcFunctionInvocationDispatcher.Setup(m => m.FinishInitialization(functionMetadataCollection, default)).Returns(Task.FromResult(0));
200207
_mockFunctionMetadataProvider.Setup(m => m.GetFunctionMetadataAsync(workerConfigs, environment, false)).Returns(Task.FromResult(functionMetadataCollection.ToImmutableArray()));
201208

202-
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(_logger, _mockRpcFunctionInvocationDispatcher.Object, _mockFunctionMetadataProvider.Object);
209+
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(_logger, _mockRpcFunctionInvocationDispatcher.Object, _mockFunctionMetadataProvider.Object, new OptionsWrapper<ScriptJobHostOptions>(scriptjobhostoptions));
203210

204211
//Act
205212
var functions = _aggregateFunctionMetadataProvider.GetFunctionMetadataAsync(workerConfigs, environment, false).GetAwaiter().GetResult();
@@ -210,6 +217,17 @@ public void GetFunctionMetadataAsync_HostIndexing()
210217
Assert.False(functionLoadLogs.Any());
211218
}
212219

220+
[Fact]
221+
public void ValidateFunctionAppFormat_InputMixedApp()
222+
{
223+
_logger.ClearLogMessages();
224+
string scriptPath = Path.Combine(Environment.CurrentDirectory, @"..", "..", "..", "..", "..", "sample", "node");
225+
AggregateFunctionMetadataProvider.ValidateFunctionAppFormat(scriptPath, _logger);
226+
var traces = _logger.GetLogMessages();
227+
var functionLoadLogs = traces.Where(m => m.FormattedMessage.Contains("Detected mixed function app. Some functions may not be indexed"));
228+
Assert.True(functionLoadLogs.Any());
229+
}
230+
213231
[Fact]
214232
public void GetFunctionMetadataAsync_WorkerIndexing_NoHostFallback()
215233
{
@@ -221,6 +239,8 @@ public void GetFunctionMetadataAsync_WorkerIndexing_NoHostFallback()
221239

222240
var workerConfigs = TestHelpers.GetTestWorkerConfigs().ToImmutableArray();
223241
workerConfigs.ToList().ForEach(config => config.Description.WorkerIndexing = "true");
242+
var scriptjobhostoptions = new ScriptJobHostOptions();
243+
scriptjobhostoptions.RootScriptPath = Path.Combine(Environment.CurrentDirectory, @"..", "..", "..", "..", "..", "sample", "node");
224244

225245
var environment = SystemEnvironment.Instance;
226246
environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime, "node");
@@ -232,7 +252,7 @@ public void GetFunctionMetadataAsync_WorkerIndexing_NoHostFallback()
232252
_mockRpcFunctionInvocationDispatcher.Setup(m => m.FinishInitialization(functionMetadataCollection, default)).Returns(Task.FromResult(0));
233253
_mockFunctionMetadataProvider.Setup(m => m.GetFunctionMetadataAsync(workerConfigs, environment, false)).Returns(Task.FromResult(functionMetadataCollection.ToImmutableArray()));
234254

235-
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(_logger, _mockRpcFunctionInvocationDispatcher.Object, _mockFunctionMetadataProvider.Object);
255+
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(_logger, _mockRpcFunctionInvocationDispatcher.Object, _mockFunctionMetadataProvider.Object, new OptionsWrapper<ScriptJobHostOptions>(scriptjobhostoptions));
236256

237257
// Act
238258
var functions = _aggregateFunctionMetadataProvider.GetFunctionMetadataAsync(workerConfigs, environment, false).GetAwaiter().GetResult();
@@ -244,6 +264,47 @@ public void GetFunctionMetadataAsync_WorkerIndexing_NoHostFallback()
244264
Assert.True(functions.Count() == 0);
245265
}
246266

267+
[Fact]
268+
public void GetFunctionMetadataAsync_InputMixedApp()
269+
{
270+
// Arrange
271+
_logger.ClearLogMessages();
272+
273+
IEnumerable<RawFunctionMetadata> rawFunctionMetadataCollection = new List<RawFunctionMetadata>();
274+
var functionMetadataCollection = new List<FunctionMetadata>();
275+
functionMetadataCollection.Add(GetTestFunctionMetadata());
276+
277+
var workerConfigs = TestHelpers.GetTestWorkerConfigs().ToImmutableArray();
278+
workerConfigs.ToList().ForEach(config => config.Description.WorkerIndexing = "true");
279+
var scriptjobhostoptions = new ScriptJobHostOptions();
280+
scriptjobhostoptions.RootScriptPath = Path.Combine(Environment.CurrentDirectory, @"..", "..", "..", "..", "..", "sample", "node");
281+
282+
var environment = SystemEnvironment.Instance;
283+
environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime, "node");
284+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, "EnableWorkerIndexing");
285+
286+
_mockRpcFunctionInvocationDispatcher.Setup(m => m.InitializeAsync(functionMetadataCollection, default)).Returns(Task.FromResult(0));
287+
_mockRpcFunctionInvocationDispatcher.Setup(m => m.GetWorkerMetadata()).Returns(Task.FromResult(rawFunctionMetadataCollection));
288+
289+
_aggregateFunctionMetadataProvider = new AggregateFunctionMetadataProvider(
290+
_logger,
291+
_mockRpcFunctionInvocationDispatcher.Object,
292+
_mockFunctionMetadataProvider.Object,
293+
new OptionsWrapper<ScriptJobHostOptions>(scriptjobhostoptions));
294+
295+
// Act
296+
var functions = _aggregateFunctionMetadataProvider.GetFunctionMetadataAsync(workerConfigs, environment, false).GetAwaiter().GetResult();
297+
298+
// Assert
299+
string expectedLog = "Detected mixed function app. Some functions may not be indexed";
300+
var traces = _logger.GetLogMessages();
301+
Assert.False(traces.Where(m => m.FormattedMessage.Contains(expectedLog)).Any());
302+
303+
Task.Delay(TimeSpan.FromSeconds(65)).Wait();
304+
traces = _logger.GetLogMessages();
305+
Assert.True(traces.Where(m => m.FormattedMessage.Contains(expectedLog)).Any());
306+
}
307+
247308
private static RawFunctionMetadata GetTestRawFunctionMetadata(bool useDefaultMetadataIndexing)
248309
{
249310
return new RawFunctionMetadata()

0 commit comments

Comments
 (0)