Skip to content

Commit 395b0c9

Browse files
authored
Worker config updates to make path to jave.exe configurable (#3210)
* Add support to configure Java.exe path
1 parent 5a7a17c commit 395b0c9

File tree

9 files changed

+157
-38
lines changed

9 files changed

+157
-38
lines changed

src/WebJobs.Script.Grpc/Abstractions/WorkerDescription.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@ public class WorkerDescription
1212
/// <summary>
1313
/// Gets or sets the name of the supported language. This is the same name as the IConfiguration section for the worker.
1414
/// </summary>
15-
[JsonProperty(PropertyName = "language", Required = Required.Always)]
15+
[JsonProperty(PropertyName = "language")]
1616
public string Language { get; set; }
1717

1818
/// <summary>
1919
/// Gets or sets the supported file extension type. Functions are registered with workers based on extension.
2020
/// </summary>
21-
[JsonProperty(PropertyName = "extension", Required = Required.Always)]
21+
[JsonProperty(PropertyName = "extension")]
2222
public string Extension { get; set; }
2323

2424
/// <summary>
2525
/// Gets or sets the default executable path.
2626
/// </summary>
27-
[JsonProperty(PropertyName = "defaultExecutablePath", Required = Required.Always)]
27+
[JsonProperty(PropertyName = "defaultExecutablePath")]
2828
public string DefaultExecutablePath { get; set; }
2929

3030
/// <summary>
3131
/// Gets or sets the default path to the worker (relative to the bin/workers/{language} directory)
3232
/// </summary>
33-
[JsonProperty(PropertyName = "defaultWorkerPath", Required = Required.Always)]
33+
[JsonProperty(PropertyName = "defaultWorkerPath")]
3434
public string DefaultWorkerPath { get; set; }
3535

3636
/// <summary>

src/WebJobs.Script/Rpc/Configuration/WorkerConfigFactory.cs

+60-14
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public IEnumerable<WorkerConfig> GetConfigs(IEnumerable<IWorkerProvider> provide
5050

5151
if (description.Language.Equals(LanguageWorkerConstants.JavaLanguageWorkerName))
5252
{
53-
arguments.ExecutablePath = GetExecutablePathForJava();
53+
arguments.ExecutablePath = GetExecutablePathForJava(description.DefaultExecutablePath);
5454
}
5555

5656
if (provider.TryConfigureArguments(arguments, _config, _logger))
@@ -117,6 +117,7 @@ internal void AddProvider(string workerDir, ILogger logger)
117117
{
118118
try
119119
{
120+
Dictionary<string, WorkerDescription> descriptionProfiles = new Dictionary<string, WorkerDescription>();
120121
string workerConfigPath = Path.Combine(workerDir, LanguageWorkerConstants.WorkerConfigFileName);
121122
if (!File.Exists(workerConfigPath))
122123
{
@@ -130,11 +131,15 @@ internal void AddProvider(string workerDir, ILogger logger)
130131
workerDescription.WorkerDirectory = workerDir;
131132
var languageSection = _config.GetSection($"{LanguageWorkerConstants.LanguageWorkersSectionName}:{workerDescription.Language}");
132133
workerDescription.Arguments = workerDescription.Arguments ?? new List<string>();
133-
var argumentsSection = languageSection.GetSection($"{LanguageWorkerConstants.WorkerDescriptionArguments}");
134-
if (argumentsSection.Value != null)
134+
135+
descriptionProfiles = GetWorkerDescriptionProfiles(workerConfig);
136+
if (ScriptSettingsManager.Instance.IsAppServiceEnvironment)
135137
{
136-
workerDescription.Arguments.AddRange(Regex.Split(argumentsSection.Value, @"\s+"));
138+
//Overwrite default Description with AppServiceEnv profile
139+
workerDescription = GetWorkerDescriptionFromProfiles(LanguageWorkerConstants.WorkerDescriptionAppServiceEnvProfileName, descriptionProfiles, workerDescription);
137140
}
141+
GetDefaultExecutablePathFromAppSettings(workerDescription, languageSection);
142+
AddArgumentsFromAppSettings(workerDescription, languageSection);
138143
if (File.Exists(workerDescription.GetWorkerPath()))
139144
{
140145
logger.LogTrace($"Will load worker provider for language: {workerDescription.Language}");
@@ -151,23 +156,64 @@ internal void AddProvider(string workerDir, ILogger logger)
151156
}
152157
}
153158

154-
internal string GetExecutablePathForJava()
159+
private static Dictionary<string, WorkerDescription> GetWorkerDescriptionProfiles(JObject workerConfig)
160+
{
161+
Dictionary<string, WorkerDescription> descriptionProfiles = new Dictionary<string, WorkerDescription>();
162+
var profiles = workerConfig.Property("profiles")?.Value.ToObject<JObject>();
163+
if (profiles != null)
164+
{
165+
foreach (var profile in profiles)
166+
{
167+
string name = profile.Key;
168+
JToken value = profile.Value;
169+
WorkerDescription description = profile.Value.ToObject<WorkerDescription>();
170+
descriptionProfiles.Add(name, description);
171+
}
172+
}
173+
return descriptionProfiles;
174+
}
175+
176+
private static WorkerDescription GetWorkerDescriptionFromProfiles(string key, Dictionary<string, WorkerDescription> descriptionProfiles, WorkerDescription defaultWorkerDescription)
177+
{
178+
WorkerDescription profileDescription = null;
179+
if (descriptionProfiles.TryGetValue(key, out profileDescription))
180+
{
181+
profileDescription.Arguments = profileDescription.Arguments?.Count > 0 ? profileDescription.Arguments : defaultWorkerDescription.Arguments;
182+
profileDescription.DefaultExecutablePath = string.IsNullOrEmpty(profileDescription.DefaultExecutablePath) ? defaultWorkerDescription.DefaultExecutablePath : profileDescription.DefaultExecutablePath;
183+
profileDescription.DefaultWorkerPath = string.IsNullOrEmpty(profileDescription.DefaultWorkerPath) ? defaultWorkerDescription.DefaultWorkerPath : profileDescription.DefaultWorkerPath;
184+
profileDescription.Extension = string.IsNullOrEmpty(profileDescription.Extension) ? defaultWorkerDescription.Extension : profileDescription.Extension;
185+
profileDescription.Language = string.IsNullOrEmpty(profileDescription.Language) ? defaultWorkerDescription.Language : profileDescription.Language;
186+
profileDescription.WorkerDirectory = string.IsNullOrEmpty(profileDescription.WorkerDirectory) ? defaultWorkerDescription.WorkerDirectory : profileDescription.WorkerDirectory;
187+
return profileDescription;
188+
}
189+
return defaultWorkerDescription;
190+
}
191+
192+
private static void GetDefaultExecutablePathFromAppSettings(WorkerDescription workerDescription, IConfigurationSection languageSection)
193+
{
194+
var defaultExecutablePath = languageSection.GetSection($"{LanguageWorkerConstants.WorkerDescriptionDefaultExecutablePath}");
195+
workerDescription.DefaultExecutablePath = defaultExecutablePath.Value != null ? defaultExecutablePath.Value : workerDescription.DefaultExecutablePath;
196+
}
197+
198+
private static void AddArgumentsFromAppSettings(WorkerDescription workerDescription, IConfigurationSection languageSection)
199+
{
200+
var argumentsSection = languageSection.GetSection($"{LanguageWorkerConstants.WorkerDescriptionArguments}");
201+
if (argumentsSection.Value != null)
202+
{
203+
workerDescription.Arguments.AddRange(Regex.Split(argumentsSection.Value, @"\s+"));
204+
}
205+
}
206+
207+
internal string GetExecutablePathForJava(string defaultExecutablePath)
155208
{
156209
string javaHome = ScriptSettingsManager.Instance.GetSetting("JAVA_HOME");
157210
if (string.IsNullOrEmpty(javaHome))
158211
{
159-
return LanguageWorkerConstants.JavaLanguageWorkerName;
212+
return defaultExecutablePath;
160213
}
161214
else
162215
{
163-
if (ScriptSettingsManager.Instance.IsAppServiceEnvironment)
164-
{
165-
return Path.GetFullPath(Path.Combine(javaHome, "..", LanguageWorkerConstants.AppServiceEnvJavaVersion, "bin", LanguageWorkerConstants.JavaLanguageWorkerName));
166-
}
167-
else
168-
{
169-
return Path.GetFullPath(Path.Combine(javaHome, "bin", LanguageWorkerConstants.JavaLanguageWorkerName));
170-
}
216+
return Path.GetFullPath(Path.Combine(javaHome, "bin", defaultExecutablePath));
171217
}
172218
}
173219
}

src/WebJobs.Script/Rpc/LanguageWorkerChannel.cs

+8-4
Original file line numberDiff line numberDiff line change
@@ -337,14 +337,18 @@ internal void StartProcess(string workerId, Process process)
337337
// TODO: per language stdout/err parser?
338338
if (e.Data != null)
339339
{
340-
if (e.Data.IndexOf("warn", StringComparison.OrdinalIgnoreCase) > 0)
340+
if (e.Data.IndexOf("warn", StringComparison.OrdinalIgnoreCase) > -1)
341341
{
342342
_logger.LogWarning(e.Data);
343343
}
344-
else
344+
else if (e.Data.IndexOf("error", StringComparison.OrdinalIgnoreCase) > -1)
345345
{
346346
_logger.LogError(e.Data);
347347
}
348+
else
349+
{
350+
_logger.LogInformation(e.Data);
351+
}
348352
}
349353
};
350354
process.OutputDataReceived += (sender, e) =>
@@ -371,9 +375,9 @@ internal void StartProcess(string workerId, Process process)
371375
HandleWorkerError(new Exception("Worker process is not attached"));
372376
}
373377
};
374-
_logger.LogInformation($"Starting {process.StartInfo.FileName} language worker process with Arguments={process.StartInfo.Arguments}");
378+
_logger.LogInformation($"Starting language worker process: {process.StartInfo.FileName} {process.StartInfo.Arguments}");
375379
process.Start();
376-
_logger.LogInformation($"{process.StartInfo.FileName} process with Id={process.Id} started");
380+
_logger.LogInformation($"{process.StartInfo.FileName} process with Id: {process.Id} started");
377381
process.BeginErrorReadLine();
378382
process.BeginOutputReadLine();
379383
}

src/WebJobs.Script/Rpc/LanguageWorkerConstants.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public static class LanguageWorkerConstants
88
public const string FunctionWorkerRuntimeSettingName = "FUNCTIONS_WORKER_RUNTIME";
99
public const string DotNetLanguageWorkerName = "dotnet";
1010
public const string NodeLanguageWorkerName = "node";
11+
public const string JavaLanguageWorkerName = "java";
1112
public const string WorkerConfigFileName = "worker.config.json";
1213
public const string DefaultWorkersDirectoryName = "workers";
1314

@@ -24,8 +25,8 @@ public static class LanguageWorkerConstants
2425
public const string WorkerDescription = "Description";
2526
public const string WorkerDescriptionArguments = "arguments";
2627

27-
// Java
28-
public const string AppServiceEnvJavaVersion = "zulu8.23.0.3-jdk8.0.144-win_x64";
29-
public const string JavaLanguageWorkerName = "java";
28+
// Profiles
29+
public const string WorkerDescriptionProfiles = "profiles";
30+
public const string WorkerDescriptionAppServiceEnvProfileName = "AppServiceEnvironment";
3031
}
3132
}

src/WebJobs.Script/WebJobs.Script.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<NoWarn>NU1701</NoWarn>
3232
</PackageReference>
3333
<PackageReference Include="Microsoft.Azure.AppService.Proxy.Client" Version="2.0.5350001-beta-fc119b98" />
34-
<PackageReference Include="Microsoft.Azure.Functions.JavaWorker" Version="1.1.0-beta8" />
34+
<PackageReference Include="Microsoft.Azure.Functions.JavaWorker" Version="1.1.0-beta9" />
3535
<PackageReference Include="Microsoft.Azure.Functions.NodeJsWorker" Version="1.0.0-beta3" />
3636
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.0-beta7-11351" />
3737
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.0-beta7-10629" />

test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public async Task HttpTrigger_Get_Succeeds()
201201
await InvokeHttpTrigger("HttpTrigger");
202202
}
203203

204-
[Fact(Skip = "Investigate test failure")]
204+
[Fact]
205205
public async Task HttpTrigger_Java_Get_Succeeds()
206206
{
207207
await InvokeHttpTrigger("HttpTrigger-Java");

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
2929
<PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="1.9.1" />
3030
<PackageReference Include="Microsoft.Azure.EventHubs" Version="1.0.3" />
31-
<PackageReference Include="Microsoft.Azure.Functions.JavaWorker" Version="1.1.0-beta8" />
31+
<PackageReference Include="Microsoft.Azure.Functions.JavaWorker" Version="1.1.0-beta9" />
3232
<PackageReference Include="Microsoft.Azure.Functions.NodeJsWorker" Version="1.0.0-beta3" />
3333
<PackageReference Include="Microsoft.Azure.Mobile.Client" Version="4.0.2" />
3434
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="2.0.0" />

test/WebJobs.Script.Tests/Rpc/GenericWorkerProviderTests.cs

+75-5
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,54 @@ public void ReadWorkerProviderFromConfig_InvalidWorker()
172172
Assert.Empty(providers);
173173
}
174174

175-
private IEnumerable<IWorkerProvider> TestReadWorkerProviderFromConfig(IEnumerable<TestLanguageWorkerConfig> configs, ILogger testLogger, string language = null, Dictionary<string, string> keyValuePairs = null)
175+
[Fact]
176+
public void ReadWorkerProviderFromConfig_AddAppSvcProfile_ReturnsAppServiceEnvDescription()
177+
{
178+
var expectedArguments = new string[] { "-v", "verbose" };
179+
var configs = new List<TestLanguageWorkerConfig>() { MakeTestConfig(testLanguage, expectedArguments, false, LanguageWorkerConstants.WorkerDescriptionAppServiceEnvProfileName) };
180+
var testLogger = new TestLogger(testLanguage);
181+
182+
// Creates temp directory w/ worker.config.json and runs ReadWorkerProviderFromConfig
183+
IEnumerable<IWorkerProvider> providers = TestReadWorkerProviderFromConfig(configs, testLogger, null, null, true);
184+
Assert.Single(providers);
185+
186+
IWorkerProvider worker = providers.FirstOrDefault();
187+
Assert.Equal("myFooPath", worker.GetDescription().DefaultExecutablePath);
188+
}
189+
190+
[Fact]
191+
public void ReadWorkerProviderFromConfig_AddProfile_ReturnsDefaultDescription()
192+
{
193+
var expectedArguments = new string[] { "-v", "verbose" };
194+
var configs = new List<TestLanguageWorkerConfig>() { MakeTestConfig(testLanguage, expectedArguments, false, "TestProfile") };
195+
var testLogger = new TestLogger(testLanguage);
196+
197+
// Creates temp directory w/ worker.config.json and runs ReadWorkerProviderFromConfig
198+
IEnumerable<IWorkerProvider> providers = TestReadWorkerProviderFromConfig(configs, testLogger);
199+
Assert.Single(providers);
200+
201+
IWorkerProvider worker = providers.FirstOrDefault();
202+
Assert.Equal("foopath", worker.GetDescription().DefaultExecutablePath);
203+
}
204+
205+
[Fact]
206+
public void ReadWorkerProviderFromConfig_OverrideDefaultExePath()
207+
{
208+
var configs = new List<TestLanguageWorkerConfig>() { MakeTestConfig(testLanguage, new string[0], false, LanguageWorkerConstants.WorkerDescriptionAppServiceEnvProfileName) };
209+
var testLogger = new TestLogger(testLanguage);
210+
var testExePath = "./mySrc/myIndex";
211+
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>
212+
{
213+
[$"{LanguageWorkerConstants.LanguageWorkersSectionName}:{testLanguage}:{LanguageWorkerConstants.WorkerDescriptionDefaultExecutablePath}"] = testExePath
214+
};
215+
var providers = TestReadWorkerProviderFromConfig(configs, new TestLogger(testLanguage), null, keyValuePairs, true);
216+
Assert.Single(providers);
217+
218+
IWorkerProvider worker = providers.FirstOrDefault();
219+
Assert.Equal(testExePath, worker.GetDescription().DefaultExecutablePath);
220+
}
221+
222+
private IEnumerable<IWorkerProvider> TestReadWorkerProviderFromConfig(IEnumerable<TestLanguageWorkerConfig> configs, ILogger testLogger, string language = null, Dictionary<string, string> keyValuePairs = null, bool appSvcEnv = false)
176223
{
177224
var workerPathSection = $"{LanguageWorkerConstants.LanguageWorkersSectionName}:{LanguageWorkerConstants.WorkersDirectorySectionName}";
178225
try
@@ -187,7 +234,17 @@ private IEnumerable<IWorkerProvider> TestReadWorkerProviderFromConfig(IEnumerabl
187234
var scriptHostConfig = new ScriptHostConfiguration();
188235
var scriptSettingsManager = new ScriptSettingsManager(config);
189236
var configFactory = new WorkerConfigFactory(config, testLogger);
190-
237+
if (appSvcEnv)
238+
{
239+
var testEnvVariables = new Dictionary<string, string>
240+
{
241+
{ EnvironmentSettingNames.AzureWebsiteInstanceId, "123" },
242+
};
243+
using (var variables = new TestScopedSettings(scriptSettingsManager, testEnvVariables))
244+
{
245+
return configFactory.GetWorkerProviders(testLogger, scriptSettingsManager, language: language);
246+
}
247+
}
191248
return configFactory.GetWorkerProviders(testLogger, scriptSettingsManager, language: language);
192249
}
193250
finally
@@ -240,17 +297,17 @@ private static IConfigurationRoot TestConfigBuilder(string workerPathSection, Di
240297
return config;
241298
}
242299

243-
private static TestLanguageWorkerConfig MakeTestConfig(string language, string[] arguments, bool invalid = false)
300+
private static TestLanguageWorkerConfig MakeTestConfig(string language, string[] arguments, bool invalid = false, string addAppSvcProfile = "")
244301
{
245-
string json = GetTestWorkerConfig(language, arguments, invalid).ToString();
302+
string json = GetTestWorkerConfig(language, arguments, invalid, addAppSvcProfile).ToString();
246303
return new TestLanguageWorkerConfig()
247304
{
248305
Json = json,
249306
Language = language,
250307
};
251308
}
252309

253-
private static JObject GetTestWorkerConfig(string language, string[] arguments, bool invalid)
310+
private static JObject GetTestWorkerConfig(string language, string[] arguments, bool invalid, string profileName)
254311
{
255312
var description = new WorkerDescription()
256313
{
@@ -263,6 +320,19 @@ private static JObject GetTestWorkerConfig(string language, string[] arguments,
263320

264321
JObject config = new JObject();
265322
config["Description"] = JObject.FromObject(description);
323+
324+
if (!string.IsNullOrEmpty(profileName))
325+
{
326+
var appSvcDescription = new WorkerDescription()
327+
{
328+
DefaultExecutablePath = "myFooPath",
329+
};
330+
331+
JObject profiles = new JObject();
332+
profiles[profileName] = JObject.FromObject(appSvcDescription);
333+
config[LanguageWorkerConstants.WorkerDescriptionProfiles] = profiles;
334+
}
335+
266336
if (invalid)
267337
{
268338
config["Description"] = "invalidWorkerConfig";

0 commit comments

Comments
 (0)