diff --git a/src/WebJobs.Script.Grpc/Abstractions/WorkerDescription.cs b/src/WebJobs.Script.Grpc/Abstractions/WorkerDescription.cs index 18650a093c..5357363b88 100644 --- a/src/WebJobs.Script.Grpc/Abstractions/WorkerDescription.cs +++ b/src/WebJobs.Script.Grpc/Abstractions/WorkerDescription.cs @@ -12,25 +12,25 @@ public class WorkerDescription /// /// Gets or sets the name of the supported language. This is the same name as the IConfiguration section for the worker. /// - [JsonProperty(PropertyName = "language", Required = Required.Always)] + [JsonProperty(PropertyName = "language")] public string Language { get; set; } /// /// Gets or sets the supported file extension type. Functions are registered with workers based on extension. /// - [JsonProperty(PropertyName = "extension", Required = Required.Always)] + [JsonProperty(PropertyName = "extension")] public string Extension { get; set; } /// /// Gets or sets the default executable path. /// - [JsonProperty(PropertyName = "defaultExecutablePath", Required = Required.Always)] + [JsonProperty(PropertyName = "defaultExecutablePath")] public string DefaultExecutablePath { get; set; } /// /// Gets or sets the default path to the worker (relative to the bin/workers/{language} directory) /// - [JsonProperty(PropertyName = "defaultWorkerPath", Required = Required.Always)] + [JsonProperty(PropertyName = "defaultWorkerPath")] public string DefaultWorkerPath { get; set; } /// diff --git a/src/WebJobs.Script/Rpc/Configuration/WorkerConfigFactory.cs b/src/WebJobs.Script/Rpc/Configuration/WorkerConfigFactory.cs index 369db456a1..36bc073214 100644 --- a/src/WebJobs.Script/Rpc/Configuration/WorkerConfigFactory.cs +++ b/src/WebJobs.Script/Rpc/Configuration/WorkerConfigFactory.cs @@ -50,7 +50,7 @@ public IEnumerable GetConfigs(IEnumerable provide if (description.Language.Equals(LanguageWorkerConstants.JavaLanguageWorkerName)) { - arguments.ExecutablePath = GetExecutablePathForJava(); + arguments.ExecutablePath = GetExecutablePathForJava(description.DefaultExecutablePath); } if (provider.TryConfigureArguments(arguments, _config, _logger)) @@ -117,6 +117,7 @@ internal void AddProvider(string workerDir, ILogger logger) { try { + Dictionary descriptionProfiles = new Dictionary(); string workerConfigPath = Path.Combine(workerDir, LanguageWorkerConstants.WorkerConfigFileName); if (!File.Exists(workerConfigPath)) { @@ -130,11 +131,15 @@ internal void AddProvider(string workerDir, ILogger logger) workerDescription.WorkerDirectory = workerDir; var languageSection = _config.GetSection($"{LanguageWorkerConstants.LanguageWorkersSectionName}:{workerDescription.Language}"); workerDescription.Arguments = workerDescription.Arguments ?? new List(); - var argumentsSection = languageSection.GetSection($"{LanguageWorkerConstants.WorkerDescriptionArguments}"); - if (argumentsSection.Value != null) + + descriptionProfiles = GetWorkerDescriptionProfiles(workerConfig); + if (ScriptSettingsManager.Instance.IsAppServiceEnvironment) { - workerDescription.Arguments.AddRange(Regex.Split(argumentsSection.Value, @"\s+")); + //Overwrite default Description with AppServiceEnv profile + workerDescription = GetWorkerDescriptionFromProfiles(LanguageWorkerConstants.WorkerDescriptionAppServiceEnvProfileName, descriptionProfiles, workerDescription); } + GetDefaultExecutablePathFromAppSettings(workerDescription, languageSection); + AddArgumentsFromAppSettings(workerDescription, languageSection); if (File.Exists(workerDescription.GetWorkerPath())) { logger.LogTrace($"Will load worker provider for language: {workerDescription.Language}"); @@ -151,23 +156,64 @@ internal void AddProvider(string workerDir, ILogger logger) } } - internal string GetExecutablePathForJava() + private static Dictionary GetWorkerDescriptionProfiles(JObject workerConfig) + { + Dictionary descriptionProfiles = new Dictionary(); + var profiles = workerConfig.Property("profiles")?.Value.ToObject(); + if (profiles != null) + { + foreach (var profile in profiles) + { + string name = profile.Key; + JToken value = profile.Value; + WorkerDescription description = profile.Value.ToObject(); + descriptionProfiles.Add(name, description); + } + } + return descriptionProfiles; + } + + private static WorkerDescription GetWorkerDescriptionFromProfiles(string key, Dictionary descriptionProfiles, WorkerDescription defaultWorkerDescription) + { + WorkerDescription profileDescription = null; + if (descriptionProfiles.TryGetValue(key, out profileDescription)) + { + profileDescription.Arguments = profileDescription.Arguments?.Count > 0 ? profileDescription.Arguments : defaultWorkerDescription.Arguments; + profileDescription.DefaultExecutablePath = string.IsNullOrEmpty(profileDescription.DefaultExecutablePath) ? defaultWorkerDescription.DefaultExecutablePath : profileDescription.DefaultExecutablePath; + profileDescription.DefaultWorkerPath = string.IsNullOrEmpty(profileDescription.DefaultWorkerPath) ? defaultWorkerDescription.DefaultWorkerPath : profileDescription.DefaultWorkerPath; + profileDescription.Extension = string.IsNullOrEmpty(profileDescription.Extension) ? defaultWorkerDescription.Extension : profileDescription.Extension; + profileDescription.Language = string.IsNullOrEmpty(profileDescription.Language) ? defaultWorkerDescription.Language : profileDescription.Language; + profileDescription.WorkerDirectory = string.IsNullOrEmpty(profileDescription.WorkerDirectory) ? defaultWorkerDescription.WorkerDirectory : profileDescription.WorkerDirectory; + return profileDescription; + } + return defaultWorkerDescription; + } + + private static void GetDefaultExecutablePathFromAppSettings(WorkerDescription workerDescription, IConfigurationSection languageSection) + { + var defaultExecutablePath = languageSection.GetSection($"{LanguageWorkerConstants.WorkerDescriptionDefaultExecutablePath}"); + workerDescription.DefaultExecutablePath = defaultExecutablePath.Value != null ? defaultExecutablePath.Value : workerDescription.DefaultExecutablePath; + } + + private static void AddArgumentsFromAppSettings(WorkerDescription workerDescription, IConfigurationSection languageSection) + { + var argumentsSection = languageSection.GetSection($"{LanguageWorkerConstants.WorkerDescriptionArguments}"); + if (argumentsSection.Value != null) + { + workerDescription.Arguments.AddRange(Regex.Split(argumentsSection.Value, @"\s+")); + } + } + + internal string GetExecutablePathForJava(string defaultExecutablePath) { string javaHome = ScriptSettingsManager.Instance.GetSetting("JAVA_HOME"); if (string.IsNullOrEmpty(javaHome)) { - return LanguageWorkerConstants.JavaLanguageWorkerName; + return defaultExecutablePath; } else { - if (ScriptSettingsManager.Instance.IsAppServiceEnvironment) - { - return Path.GetFullPath(Path.Combine(javaHome, "..", LanguageWorkerConstants.AppServiceEnvJavaVersion, "bin", LanguageWorkerConstants.JavaLanguageWorkerName)); - } - else - { - return Path.GetFullPath(Path.Combine(javaHome, "bin", LanguageWorkerConstants.JavaLanguageWorkerName)); - } + return Path.GetFullPath(Path.Combine(javaHome, "bin", defaultExecutablePath)); } } } diff --git a/src/WebJobs.Script/Rpc/LanguageWorkerChannel.cs b/src/WebJobs.Script/Rpc/LanguageWorkerChannel.cs index e34e4d3105..7290e54407 100644 --- a/src/WebJobs.Script/Rpc/LanguageWorkerChannel.cs +++ b/src/WebJobs.Script/Rpc/LanguageWorkerChannel.cs @@ -337,14 +337,18 @@ internal void StartProcess(string workerId, Process process) // TODO: per language stdout/err parser? if (e.Data != null) { - if (e.Data.IndexOf("warn", StringComparison.OrdinalIgnoreCase) > 0) + if (e.Data.IndexOf("warn", StringComparison.OrdinalIgnoreCase) > -1) { _logger.LogWarning(e.Data); } - else + else if (e.Data.IndexOf("error", StringComparison.OrdinalIgnoreCase) > -1) { _logger.LogError(e.Data); } + else + { + _logger.LogInformation(e.Data); + } } }; process.OutputDataReceived += (sender, e) => @@ -371,9 +375,9 @@ internal void StartProcess(string workerId, Process process) HandleWorkerError(new Exception("Worker process is not attached")); } }; - _logger.LogInformation($"Starting {process.StartInfo.FileName} language worker process with Arguments={process.StartInfo.Arguments}"); + _logger.LogInformation($"Starting language worker process: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); process.Start(); - _logger.LogInformation($"{process.StartInfo.FileName} process with Id={process.Id} started"); + _logger.LogInformation($"{process.StartInfo.FileName} process with Id: {process.Id} started"); process.BeginErrorReadLine(); process.BeginOutputReadLine(); } diff --git a/src/WebJobs.Script/Rpc/LanguageWorkerConstants.cs b/src/WebJobs.Script/Rpc/LanguageWorkerConstants.cs index 15d793b37b..72c3fe41ab 100644 --- a/src/WebJobs.Script/Rpc/LanguageWorkerConstants.cs +++ b/src/WebJobs.Script/Rpc/LanguageWorkerConstants.cs @@ -8,6 +8,7 @@ public static class LanguageWorkerConstants public const string FunctionWorkerRuntimeSettingName = "FUNCTIONS_WORKER_RUNTIME"; public const string DotNetLanguageWorkerName = "dotnet"; public const string NodeLanguageWorkerName = "node"; + public const string JavaLanguageWorkerName = "java"; public const string WorkerConfigFileName = "worker.config.json"; public const string DefaultWorkersDirectoryName = "workers"; @@ -24,8 +25,8 @@ public static class LanguageWorkerConstants public const string WorkerDescription = "Description"; public const string WorkerDescriptionArguments = "arguments"; - // Java - public const string AppServiceEnvJavaVersion = "zulu8.23.0.3-jdk8.0.144-win_x64"; - public const string JavaLanguageWorkerName = "java"; + // Profiles + public const string WorkerDescriptionProfiles = "profiles"; + public const string WorkerDescriptionAppServiceEnvProfileName = "AppServiceEnvironment"; } } diff --git a/src/WebJobs.Script/WebJobs.Script.csproj b/src/WebJobs.Script/WebJobs.Script.csproj index cfa9baab21..048c636139 100644 --- a/src/WebJobs.Script/WebJobs.Script.csproj +++ b/src/WebJobs.Script/WebJobs.Script.csproj @@ -31,7 +31,7 @@ NU1701 - + diff --git a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests.cs b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests.cs index 24c5cb0638..9ce2a692c7 100644 --- a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests.cs +++ b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests.cs @@ -201,7 +201,7 @@ public async Task HttpTrigger_Get_Succeeds() await InvokeHttpTrigger("HttpTrigger"); } - [Fact(Skip = "Investigate test failure")] + [Fact] public async Task HttpTrigger_Java_Get_Succeeds() { await InvokeHttpTrigger("HttpTrigger-Java"); diff --git a/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj b/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj index 037041c615..872e39e23e 100644 --- a/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj +++ b/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj @@ -28,7 +28,7 @@ - + diff --git a/test/WebJobs.Script.Tests/Rpc/GenericWorkerProviderTests.cs b/test/WebJobs.Script.Tests/Rpc/GenericWorkerProviderTests.cs index 8e10441dc6..5b65cd695b 100644 --- a/test/WebJobs.Script.Tests/Rpc/GenericWorkerProviderTests.cs +++ b/test/WebJobs.Script.Tests/Rpc/GenericWorkerProviderTests.cs @@ -172,7 +172,54 @@ public void ReadWorkerProviderFromConfig_InvalidWorker() Assert.Empty(providers); } - private IEnumerable TestReadWorkerProviderFromConfig(IEnumerable configs, ILogger testLogger, string language = null, Dictionary keyValuePairs = null) + [Fact] + public void ReadWorkerProviderFromConfig_AddAppSvcProfile_ReturnsAppServiceEnvDescription() + { + var expectedArguments = new string[] { "-v", "verbose" }; + var configs = new List() { MakeTestConfig(testLanguage, expectedArguments, false, LanguageWorkerConstants.WorkerDescriptionAppServiceEnvProfileName) }; + var testLogger = new TestLogger(testLanguage); + + // Creates temp directory w/ worker.config.json and runs ReadWorkerProviderFromConfig + IEnumerable providers = TestReadWorkerProviderFromConfig(configs, testLogger, null, null, true); + Assert.Single(providers); + + IWorkerProvider worker = providers.FirstOrDefault(); + Assert.Equal("myFooPath", worker.GetDescription().DefaultExecutablePath); + } + + [Fact] + public void ReadWorkerProviderFromConfig_AddProfile_ReturnsDefaultDescription() + { + var expectedArguments = new string[] { "-v", "verbose" }; + var configs = new List() { MakeTestConfig(testLanguage, expectedArguments, false, "TestProfile") }; + var testLogger = new TestLogger(testLanguage); + + // Creates temp directory w/ worker.config.json and runs ReadWorkerProviderFromConfig + IEnumerable providers = TestReadWorkerProviderFromConfig(configs, testLogger); + Assert.Single(providers); + + IWorkerProvider worker = providers.FirstOrDefault(); + Assert.Equal("foopath", worker.GetDescription().DefaultExecutablePath); + } + + [Fact] + public void ReadWorkerProviderFromConfig_OverrideDefaultExePath() + { + var configs = new List() { MakeTestConfig(testLanguage, new string[0], false, LanguageWorkerConstants.WorkerDescriptionAppServiceEnvProfileName) }; + var testLogger = new TestLogger(testLanguage); + var testExePath = "./mySrc/myIndex"; + Dictionary keyValuePairs = new Dictionary + { + [$"{LanguageWorkerConstants.LanguageWorkersSectionName}:{testLanguage}:{LanguageWorkerConstants.WorkerDescriptionDefaultExecutablePath}"] = testExePath + }; + var providers = TestReadWorkerProviderFromConfig(configs, new TestLogger(testLanguage), null, keyValuePairs, true); + Assert.Single(providers); + + IWorkerProvider worker = providers.FirstOrDefault(); + Assert.Equal(testExePath, worker.GetDescription().DefaultExecutablePath); + } + + private IEnumerable TestReadWorkerProviderFromConfig(IEnumerable configs, ILogger testLogger, string language = null, Dictionary keyValuePairs = null, bool appSvcEnv = false) { var workerPathSection = $"{LanguageWorkerConstants.LanguageWorkersSectionName}:{LanguageWorkerConstants.WorkersDirectorySectionName}"; try @@ -187,7 +234,17 @@ private IEnumerable TestReadWorkerProviderFromConfig(IEnumerabl var scriptHostConfig = new ScriptHostConfiguration(); var scriptSettingsManager = new ScriptSettingsManager(config); var configFactory = new WorkerConfigFactory(config, testLogger); - + if (appSvcEnv) + { + var testEnvVariables = new Dictionary + { + { EnvironmentSettingNames.AzureWebsiteInstanceId, "123" }, + }; + using (var variables = new TestScopedSettings(scriptSettingsManager, testEnvVariables)) + { + return configFactory.GetWorkerProviders(testLogger, scriptSettingsManager, language: language); + } + } return configFactory.GetWorkerProviders(testLogger, scriptSettingsManager, language: language); } finally @@ -240,9 +297,9 @@ private static IConfigurationRoot TestConfigBuilder(string workerPathSection, Di return config; } - private static TestLanguageWorkerConfig MakeTestConfig(string language, string[] arguments, bool invalid = false) + private static TestLanguageWorkerConfig MakeTestConfig(string language, string[] arguments, bool invalid = false, string addAppSvcProfile = "") { - string json = GetTestWorkerConfig(language, arguments, invalid).ToString(); + string json = GetTestWorkerConfig(language, arguments, invalid, addAppSvcProfile).ToString(); return new TestLanguageWorkerConfig() { Json = json, @@ -250,7 +307,7 @@ private static TestLanguageWorkerConfig MakeTestConfig(string language, string[] }; } - private static JObject GetTestWorkerConfig(string language, string[] arguments, bool invalid) + private static JObject GetTestWorkerConfig(string language, string[] arguments, bool invalid, string profileName) { var description = new WorkerDescription() { @@ -263,6 +320,19 @@ private static JObject GetTestWorkerConfig(string language, string[] arguments, JObject config = new JObject(); config["Description"] = JObject.FromObject(description); + + if (!string.IsNullOrEmpty(profileName)) + { + var appSvcDescription = new WorkerDescription() + { + DefaultExecutablePath = "myFooPath", + }; + + JObject profiles = new JObject(); + profiles[profileName] = JObject.FromObject(appSvcDescription); + config[LanguageWorkerConstants.WorkerDescriptionProfiles] = profiles; + } + if (invalid) { config["Description"] = "invalidWorkerConfig"; diff --git a/test/WebJobs.Script.Tests/Rpc/WorkerConfigFactoryTests.cs b/test/WebJobs.Script.Tests/Rpc/WorkerConfigFactoryTests.cs index fd832c0162..08dfa55216 100644 --- a/test/WebJobs.Script.Tests/Rpc/WorkerConfigFactoryTests.cs +++ b/test/WebJobs.Script.Tests/Rpc/WorkerConfigFactoryTests.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using Microsoft.Azure.WebJobs.Script.Abstractions; using Microsoft.Azure.WebJobs.Script.Config; using Microsoft.Azure.WebJobs.Script.Rpc; using Microsoft.Extensions.Configuration; @@ -75,7 +73,7 @@ public void JavaPath_AppServiceEnv() }; using (var variables = new TestScopedSettings(scriptSettingsManager, testEnvVariables)) { - var javaPath = configFactory.GetExecutablePathForJava(); + var javaPath = configFactory.GetExecutablePathForJava("../../zulu8.23.0.3-jdk8.0.144-win_x64/bin/java"); Assert.Equal(@"D:\Program Files\Java\zulu8.23.0.3-jdk8.0.144-win_x64\bin\java", javaPath); } } @@ -98,7 +96,7 @@ public void JavaPath_JavaHome_Set() }; using (var variables = new TestScopedSettings(scriptSettingsManager, testEnvVariables)) { - var javaPath = configFactory.GetExecutablePathForJava(); + var javaPath = configFactory.GetExecutablePathForJava("java"); Assert.Equal(@"D:\Program Files\Java\jdk1.7.0_51\bin\java", javaPath); } } @@ -121,7 +119,7 @@ public void JavaPath_JavaHome_NotSet() }; using (var variables = new TestScopedSettings(scriptSettingsManager, testEnvVariables)) { - var javaPath = configFactory.GetExecutablePathForJava(); + var javaPath = configFactory.GetExecutablePathForJava("java"); Assert.Equal("java", javaPath); } }