Skip to content

Commit

Permalink
Restrict languages if Function_Language app setting is specified (Azu…
Browse files Browse the repository at this point in the history
…re#2646)

* Filter supported functions on Language specified in  Functions_Language Appsetting
  • Loading branch information
pragnagopa authored Apr 17, 2018
1 parent 09c3565 commit 561fc95
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 191 deletions.
6 changes: 6 additions & 0 deletions src/WebJobs.Script.Grpc/Abstractions/IWorkerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@ public interface IWorkerProvider
/// <param name="logger">The startup ILogger.</param>
/// <returns>A bool that indicates if the args were configured successfully.</returns>
bool TryConfigureArguments(ArgumentsDescription args, IConfiguration config, ILogger logger);

/// <summary>
/// Get the worker directory path
/// </summary>
/// <returns>A string which is the full path to the worker directory</returns>
string GetWorkerDirectoryPath();
}
}
11 changes: 11 additions & 0 deletions src/WebJobs.Script.Grpc/Abstractions/WorkerConstants.cs
Original file line number Diff line number Diff line change
@@ -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.

namespace Microsoft.Azure.WebJobs.Script.Abstractions
{
public class WorkerConstants
{
public const string Description = "Description";
public const string Arguments = "Arguments";
}
}
69 changes: 53 additions & 16 deletions src/WebJobs.Script/Host/ScriptHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,13 @@ public void Initialize()
PreInitialize();
ApplyEnvironmentSettings();
var hostConfig = ApplyHostConfiguration();
string functionLanguage = _settingsManager.Configuration[ScriptConstants.FunctionWorkerRuntimeSettingName];
InitializeFileWatchers();
InitializeWorkers();
InitializeWorkers(functionLanguage);

var functionMetadata = LoadFunctionMetadata();
var directTypes = LoadBindingExtensions(functionMetadata, hostConfig);
InitializeFunctionDescriptors(functionMetadata);
InitializeFunctionDescriptors(functionMetadata, functionLanguage);
GenerateFunctions(directTypes);

InitializeServices();
Expand Down Expand Up @@ -492,13 +493,22 @@ private void GenerateFunctions(IEnumerable<Type> directTypes)
/// <summary>
/// Initialize function descriptors from metadata.
/// </summary>
private void InitializeFunctionDescriptors(Collection<FunctionMetadata> functionMetadata)
internal void InitializeFunctionDescriptors(Collection<FunctionMetadata> functionMetadata, string language)
{
_descriptorProviders = new List<FunctionDescriptorProvider>()
_descriptorProviders = new List<FunctionDescriptorProvider>();
if (string.IsNullOrEmpty(language))
{
new DotNetFunctionDescriptorProvider(this, ScriptConfig),
new WorkerFunctionDescriptorProvider(this, ScriptConfig, _functionDispatcher),
};
_descriptorProviders.Add(new DotNetFunctionDescriptorProvider(this, ScriptConfig));
_descriptorProviders.Add(new WorkerFunctionDescriptorProvider(this, ScriptConfig, _functionDispatcher));
}
else if (language.Equals(ScriptConstants.DotNetLanguageWorkerName, StringComparison.OrdinalIgnoreCase))
{
_descriptorProviders.Add(new DotNetFunctionDescriptorProvider(this, ScriptConfig));
}
else
{
_descriptorProviders.Add(new WorkerFunctionDescriptorProvider(this, ScriptConfig, _functionDispatcher));
}

Collection<FunctionDescriptor> functions;
using (_metricsLogger.LatencyEvent(MetricEventNames.HostStartupGetFunctionDescriptorsLatency))
Expand Down Expand Up @@ -685,7 +695,7 @@ private JObject ApplyHostConfiguration()
return hostConfigObject;
}

private void InitializeWorkers()
private void InitializeWorkers(string language)
{
var serverImpl = new FunctionRpcService(EventManager);
var server = new GrpcServer(serverImpl);
Expand Down Expand Up @@ -715,13 +725,36 @@ private void InitializeWorkers()
server.Uri,
_hostConfig.LoggerFactory);
};
var providers = new List<IWorkerProvider>()
{
new NodeWorkerProvider(),
new JavaWorkerProvider()
};

providers.AddRange(GenericWorkerProvider.ReadWorkerProviderFromConfig(ScriptConfig, _startupLogger));
var providers = new List<IWorkerProvider>();
if (!string.IsNullOrEmpty(language))
{
_startupLogger.LogInformation($"{ScriptConstants.FunctionWorkerRuntimeSettingName} is specified, only {language} will be enabled");
// TODO: We still have some hard coded languages, so we need to handle them. Remove this switch once we've moved away from that.
switch (language.ToLower())
{
case ScriptConstants.NodeLanguageWrokerName:
providers.Add(new NodeWorkerProvider());
break;
case ScriptConstants.JavaLanguageWrokerName:
providers.Add(new JavaWorkerProvider());
break;
case ScriptConstants.DotNetLanguageWorkerName:
// No-Op
break;
default:
// Pass the language to the provider loader to filter
providers.AddRange(GenericWorkerProvider.ReadWorkerProviderFromConfig(ScriptConfig, _startupLogger, language: language));
break;
}
}
else
{
// load all providers if no specific language is specified
providers.Add(new NodeWorkerProvider());
providers.Add(new JavaWorkerProvider());
providers.AddRange(GenericWorkerProvider.ReadWorkerProviderFromConfig(ScriptConfig, _startupLogger));
}

var configFactory = new WorkerConfigFactory(ScriptSettingsManager.Instance.Configuration, _startupLogger);
var workerConfigs = configFactory.GetConfigs(providers);
Expand Down Expand Up @@ -1366,12 +1399,16 @@ internal Collection<FunctionDescriptor> GetFunctionDescriptors(IEnumerable<Funct
}
}

ValidateFunction(descriptor, httpFunctions);

if (descriptor != null)
{
ValidateFunction(descriptor, httpFunctions);
functionDescriptors.Add(descriptor);
}
else
{
string functionLanguage = _settingsManager.Configuration[ScriptConstants.FunctionWorkerRuntimeSettingName];
throw new ArgumentException($"Could not find a valid provider. {ScriptConstants.FunctionWorkerRuntimeSettingName} Appsetting is set to {functionLanguage}. Check that you have the correct language provider enabled and installed");
}
}
catch (Exception ex)
{
Expand Down
88 changes: 55 additions & 33 deletions src/WebJobs.Script/Rpc/Configuration/GenericWorkerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,66 +13,88 @@ namespace Microsoft.Azure.WebJobs.Script.Rpc
{
internal class GenericWorkerProvider : IWorkerProvider
{
private WorkerDescription workerDescription;
private List<string> arguments;
private WorkerDescription _workerDescription;
private List<string> _arguments;
private string _pathToWorkerDir;

public GenericWorkerProvider(WorkerDescription workerDescription, List<string> arguments)
public GenericWorkerProvider(WorkerDescription workerDescription, List<string> arguments, string pathToWorkerDir)
{
this.workerDescription = workerDescription ?? throw new ArgumentNullException(nameof(workerDescription));
this.arguments = arguments ?? throw new ArgumentNullException(nameof(arguments));
_workerDescription = workerDescription ?? throw new ArgumentNullException(nameof(workerDescription));
_arguments = arguments ?? throw new ArgumentNullException(nameof(arguments));
_pathToWorkerDir = pathToWorkerDir ?? throw new ArgumentNullException(nameof(pathToWorkerDir));
}

public WorkerDescription GetDescription()
{
return this.workerDescription;
return _workerDescription;
}

public bool TryConfigureArguments(ArgumentsDescription args, IConfiguration config, ILogger logger)
{
args.ExecutableArguments.AddRange(arguments);
args.ExecutableArguments.AddRange(_arguments);
return true;
}

public static List<IWorkerProvider> ReadWorkerProviderFromConfig(ScriptHostConfiguration config, ILogger logger, ScriptSettingsManager settingsManager = null)
public string GetWorkerDirectoryPath()
{
return _pathToWorkerDir;
}

public static List<IWorkerProvider> ReadWorkerProviderFromConfig(ScriptHostConfiguration config, ILogger logger, ScriptSettingsManager settingsManager = null, string language = null)
{
var providers = new List<IWorkerProvider>();
settingsManager = settingsManager ?? ScriptSettingsManager.Instance;
var assemblyDir = Path.GetDirectoryName(new Uri(typeof(WorkerConfigFactory).Assembly.CodeBase).LocalPath);

var workerDirPath = settingsManager.Configuration.GetSection("workers:config:path").Value ?? Path.Combine(assemblyDir, "workers");
var workerDirPath = settingsManager.Configuration.GetSection("workers:config:path").Value ?? WorkerProviderHelper.GetDefaultWorkerDirectoryPath();

foreach (var workerDir in Directory.EnumerateDirectories(workerDirPath))
if (!string.IsNullOrEmpty(language))
{
logger.LogInformation($"Reading Worker config for the lanuage: {language}");
string languageWorkerDirectory = Path.Combine(workerDirPath, language);
var provider = GetProviderFromConfig(languageWorkerDirectory, logger);
if (provider != null)
{
providers.Add(provider);
logger.LogTrace($"Successfully added WorkerProvider for: {language}");
}
}
else
{
try
logger.LogInformation($"Loading all the worker providers from the default workers directory: {workerDirPath}");
foreach (var workerDir in Directory.EnumerateDirectories(workerDirPath))
{
// check if worker config exists
string workerConfigPath = Path.Combine(workerDir, ScriptConstants.WorkerConfigFileName); // TODO: Move to constant
if (!File.Exists(workerConfigPath))
var provider = GetProviderFromConfig(workerDir, logger);
if (provider != null)
{
// not a worker directory
continue;
providers.Add(provider);
}
}
}
return providers;
}

public static IWorkerProvider GetProviderFromConfig(string workerDir, ILogger logger)
{
try
{
string workerConfigPath = Path.Combine(workerDir, ScriptConstants.WorkerConfigFileName);
if (File.Exists(workerConfigPath))
{
logger.LogInformation($"Found worker config: {workerConfigPath}");
string json = File.ReadAllText(workerConfigPath);
JObject workerConfig = JObject.Parse(json);

WorkerDescription description = workerConfig.Property("Description").Value.ToObject<WorkerDescription>();
// var workerSettings = settingsManager.Configuration.GetSection($"workers:{description.Language}");

WorkerDescription workerDescription = workerConfig.Property(WorkerConstants.Description).Value.ToObject<WorkerDescription>();
var arguments = new List<string>();
arguments.AddRange(workerConfig.Property("Arguments").Value.ToObject<string[]>());

var provider = new GenericWorkerProvider(description, arguments);

providers.Add(provider);
}
catch (Exception e)
{
logger.LogCritical(e, $"Failed to initialize worker provider for: {workerDir}");
arguments.AddRange(workerConfig.Property(WorkerConstants.Arguments).Value.ToObject<string[]>());
logger.LogInformation($"Will load worker provider for language: {workerDescription.Language}");
return new GenericWorkerProvider(workerDescription, arguments, workerDir);
}
return null;
}
catch (Exception ex)
{
logger?.LogError(ex, $"Failed to initialize worker provider for: {workerDir}");
return null;
}

return providers;
}
}
}
14 changes: 9 additions & 5 deletions src/WebJobs.Script/Rpc/Configuration/JavaWorkerProvider.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.IO;
using Microsoft.Azure.WebJobs.Script.Abstractions;
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.WebJobs.Script.Rpc
{
internal class JavaWorkerProvider : IWorkerProvider
{
private string pathToWorkerDir = WorkerProviderHelper.BuildWorkerDirectoryPath(ScriptConstants.JavaLanguageWrokerName);

public WorkerDescription GetDescription() => new WorkerDescription
{
Language = "Java",
Language = ScriptConstants.JavaLanguageWrokerName,
Extension = ".jar",
DefaultWorkerPath = "azure-functions-java-worker.jar",
};
Expand All @@ -28,7 +27,7 @@ public bool TryConfigureArguments(ArgumentsDescription args, IConfiguration conf
config.Bind(env);
if (string.IsNullOrEmpty(env.JAVA_HOME))
{
logger.LogError("Unable to configure java worker. Could not find JAVA_HOME app setting.");
logger.LogTrace("Unable to configure java worker. Could not find JAVA_HOME app setting.");
return false;
}

Expand All @@ -55,6 +54,11 @@ public bool TryConfigureArguments(ArgumentsDescription args, IConfiguration conf
return true;
}

public string GetWorkerDirectoryPath()
{
return pathToWorkerDir;
}

private class JavaEnvironment
{
public string JAVA_HOME { get; set; } = string.Empty;
Expand Down
9 changes: 8 additions & 1 deletion src/WebJobs.Script/Rpc/Configuration/NodeWorkerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ namespace Microsoft.Azure.WebJobs.Script.Rpc
{
internal class NodeWorkerProvider : IWorkerProvider
{
private string pathToWorkerDir = WorkerProviderHelper.BuildWorkerDirectoryPath(ScriptConstants.NodeLanguageWrokerName);

public WorkerDescription GetDescription() => new WorkerDescription
{
Language = "Node",
Language = ScriptConstants.NodeLanguageWrokerName,
Extension = ".js",
DefaultExecutablePath = "node",
DefaultWorkerPath = Path.Combine("dist", "src", "nodejsWorker.js"),
Expand All @@ -33,5 +35,10 @@ public bool TryConfigureArguments(ArgumentsDescription args, IConfiguration conf
}
return true;
}

public string GetWorkerDirectoryPath()
{
return pathToWorkerDir;
}
}
}
20 changes: 3 additions & 17 deletions src/WebJobs.Script/Rpc/Configuration/WorkerConfigFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ internal class WorkerConfigFactory
{
private readonly IConfiguration _config;
private readonly ILogger _logger;
private readonly string _assemblyDir;

public WorkerConfigFactory(IConfiguration config, ILogger logger)
{
_config = config;
_logger = logger;
_assemblyDir = Path.GetDirectoryName(new Uri(typeof(WorkerConfigFactory).Assembly.CodeBase).LocalPath);
}

public IEnumerable<WorkerConfig> GetConfigs(IEnumerable<IWorkerProvider> providers)
Expand All @@ -31,20 +29,8 @@ public IEnumerable<WorkerConfig> GetConfigs(IEnumerable<IWorkerProvider> provide
var description = provider.GetDescription();
var languageSection = _config.GetSection($"workers:{description.Language}");

// Resolve worker path
// 1. If workers.{language}.path is set, use that explicitly
// 2. If workers.path is set, use that as the base directory + language + default path
// 3. Else, use the default workers directory

// get explicit worker path from config, or build relative path from default
var workerPath = languageSection.GetSection("path").Value;
if (string.IsNullOrEmpty(workerPath))
{
var baseWorkerPath = !string.IsNullOrEmpty(_config.GetSection("workers:path").Value) ?
_config.GetSection("workers:path").Value :
Path.Combine(_assemblyDir, "workers");
workerPath = Path.Combine(baseWorkerPath, description.Language.ToLower(), description.DefaultWorkerPath);
}
// Can override the path we load from, or we use the default path from where we loaded the config
var workerPath = languageSection.GetSection("path").Value ?? Path.Combine(provider.GetWorkerDirectoryPath(), description.DefaultWorkerPath);

var arguments = new ArgumentsDescription()
{
Expand All @@ -62,7 +48,7 @@ public IEnumerable<WorkerConfig> GetConfigs(IEnumerable<IWorkerProvider> provide
}
else
{
_logger.LogError($"Could not configure language worker {description.Language}.");
_logger.LogTrace($"Could not configure language worker {description.Language}.");
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/WebJobs.Script/Rpc/Configuration/WorkerProviderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.IO;

namespace Microsoft.Azure.WebJobs.Script.Rpc
{
internal class WorkerProviderHelper
{
public static string BuildWorkerDirectoryPath(string languageName)
{
return Path.Combine(Path.GetDirectoryName(new Uri(typeof(WorkerConfigFactory).Assembly.CodeBase).LocalPath), ScriptConstants.DefaultWorkersDirectoryName, languageName);
}

public static string GetDefaultWorkerDirectoryPath()
{
return Path.Combine(Path.GetDirectoryName(new Uri(typeof(WorkerConfigFactory).Assembly.CodeBase).LocalPath), ScriptConstants.DefaultWorkersDirectoryName);
}
}
}
Loading

0 comments on commit 561fc95

Please sign in to comment.