Skip to content

Commit ebc3adf

Browse files
maiqbal11pragnagopa
authored andcommitted
Hydrate worker path with version/os/arch (#4985)
1 parent 5400db5 commit ebc3adf

File tree

10 files changed

+457
-15
lines changed

10 files changed

+457
-15
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,36 @@ public class WorkerDescription
1515
[JsonProperty(PropertyName = "language")]
1616
public string Language { get; set; }
1717

18+
/// <summary>
19+
/// Gets or sets the default runtime version.
20+
/// </summary>
21+
[JsonProperty(PropertyName = "defaultRuntimeVersion")]
22+
public string DefaultRuntimeVersion { get; set; }
23+
1824
/// <summary>
1925
/// Gets or sets the supported file extension type. Functions are registered with workers based on extension.
2026
/// </summary>
2127
[JsonProperty(PropertyName = "extensions")]
2228
public List<string> Extensions { get; set; }
2329

30+
/// <summary>
31+
/// Gets or sets the supported architectures for this runtime.
32+
/// </summary>
33+
[JsonProperty(PropertyName = "supportedArchitectures")]
34+
public List<string> SupportedArchitectures { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the supported operating systems for this runtime.
38+
/// </summary>
39+
[JsonProperty(PropertyName = "supportedOperatingSystems")]
40+
public List<string> SupportedOperatingSystems { get; set; }
41+
42+
/// <summary>
43+
/// Gets or sets the supported versions for this runtime.
44+
/// </summary>
45+
[JsonProperty(PropertyName = "supportedRuntimeVersions")]
46+
public List<string> SupportedRuntimeVersions { get; set; }
47+
2448
/// <summary>
2549
/// Gets or sets the default executable path.
2650
/// </summary>

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System;
45
using System.ComponentModel.DataAnnotations;
6+
using System.Linq;
7+
using System.Runtime.InteropServices;
58

69
namespace Microsoft.Azure.WebJobs.Script.Abstractions
710
{
811
public static class WorkerDescriptionExtensions
912
{
13+
public const string OSPlaceholder = "{os}";
14+
public const string ArchitecturePlaceholder = "{architecture}";
15+
public const string RuntimeVersionPlaceholder = "%FUNCTIONS_WORKER_RUNTIME_VERSION%";
16+
1017
public static void Validate(this WorkerDescription workerDescription)
1118
{
1219
if (string.IsNullOrEmpty(workerDescription.Language))
@@ -22,5 +29,51 @@ public static void Validate(this WorkerDescription workerDescription)
2229
throw new ValidationException($"WorkerDescription {nameof(workerDescription.DefaultExecutablePath)} cannot be empty");
2330
}
2431
}
32+
33+
public static void ValidateWorkerPath(this WorkerDescription description, string workerPath, OSPlatform os, Architecture architecture, string version)
34+
{
35+
string language = description.Language;
36+
if (workerPath.Contains(OSPlaceholder))
37+
{
38+
ValidateOSPlatform(description, os);
39+
}
40+
41+
if (workerPath.Contains(ArchitecturePlaceholder))
42+
{
43+
ValidateArchitecture(description, architecture);
44+
}
45+
46+
if (workerPath.Contains(RuntimeVersionPlaceholder) && !string.IsNullOrEmpty(version))
47+
{
48+
ValidateRuntimeVersion(description, version);
49+
}
50+
}
51+
52+
private static void ValidateOSPlatform(this WorkerDescription description, OSPlatform os)
53+
{
54+
string language = description.Language;
55+
if (!description.SupportedOperatingSystems.Any(s => s.Equals(os.ToString(), StringComparison.OrdinalIgnoreCase)))
56+
{
57+
throw new PlatformNotSupportedException($"OS {os.ToString()} is not supported for language {language}");
58+
}
59+
}
60+
61+
private static void ValidateArchitecture(this WorkerDescription description, Architecture architecture)
62+
{
63+
string language = description.Language;
64+
if (!description.SupportedArchitectures.Any(s => s.Equals(architecture.ToString(), StringComparison.OrdinalIgnoreCase)))
65+
{
66+
throw new PlatformNotSupportedException($"Architecture {architecture.ToString()} is not supported for language {language}");
67+
}
68+
}
69+
70+
private static void ValidateRuntimeVersion(this WorkerDescription description, string version)
71+
{
72+
string language = description.Language;
73+
if (!description.SupportedRuntimeVersions.Any(s => s.Equals(version, StringComparison.OrdinalIgnoreCase)))
74+
{
75+
throw new NotSupportedException($"Version {version} is not supported for language {language}");
76+
}
77+
}
2578
}
2679
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System.Collections;
45
using System.Collections.Generic;
56
using Microsoft.Extensions.Configuration;
67
using Microsoft.Extensions.Logging;
@@ -12,16 +13,19 @@ internal class LanguageWorkerOptionsSetup : IConfigureOptions<LanguageWorkerOpti
1213
{
1314
private IConfiguration _configuration;
1415
private ILogger _logger;
16+
private IEnvironment _environment;
1517

16-
public LanguageWorkerOptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory)
18+
public LanguageWorkerOptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, IEnvironment environment)
1719
{
1820
_configuration = configuration;
1921
_logger = loggerFactory.CreateLogger("Host.LanguageWorkerConfig");
22+
_environment = environment;
2023
}
2124

2225
public void Configure(LanguageWorkerOptions options)
2326
{
24-
var configFactory = new WorkerConfigFactory(_configuration, _logger);
27+
ISystemRuntimeInformation systemRuntimeInfo = new SystemRuntimeInformation();
28+
var configFactory = new WorkerConfigFactory(_configuration, _logger, systemRuntimeInfo, _environment);
2529
options.WorkerConfigs = configFactory.GetConfigs();
2630
}
2731
}

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Runtime.InteropServices;
89
using System.Text.RegularExpressions;
910
using Microsoft.Azure.WebJobs.Script.Abstractions;
1011
using Microsoft.Azure.WebJobs.Script.Config;
@@ -19,12 +20,16 @@ internal class WorkerConfigFactory
1920
{
2021
private readonly IConfiguration _config;
2122
private readonly ILogger _logger;
23+
private readonly ISystemRuntimeInformation _systemRuntimeInformation;
24+
private readonly IEnvironment _environment;
2225
private Dictionary<string, IWorkerProvider> _workerProviderDictionary = new Dictionary<string, IWorkerProvider>();
2326

24-
public WorkerConfigFactory(IConfiguration config, ILogger logger)
27+
public WorkerConfigFactory(IConfiguration config, ILogger logger, ISystemRuntimeInformation systemRuntimeInfo, IEnvironment environment)
2528
{
2629
_config = config ?? throw new ArgumentNullException(nameof(config));
2730
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
31+
_systemRuntimeInformation = systemRuntimeInfo ?? throw new ArgumentNullException(nameof(systemRuntimeInfo));
32+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
2833
WorkersDirPath = Path.Combine(Path.GetDirectoryName(new Uri(typeof(WorkerConfigFactory).Assembly.CodeBase).LocalPath), LanguageWorkerConstants.DefaultWorkersDirectoryName);
2934
var workersDirectorySection = _config.GetSection($"{LanguageWorkerConstants.LanguageWorkersSectionName}:{LanguageWorkerConstants.WorkersDirectorySectionName}");
3035
if (!string.IsNullOrEmpty(workersDirectorySection.Value))
@@ -47,10 +52,17 @@ public IList<WorkerConfig> GetConfigs()
4752
var description = provider.GetDescription();
4853
_logger.LogDebug($"Worker path for language worker {description.Language}: {description.WorkerDirectory}");
4954

55+
string workerPath = description.GetWorkerPath();
56+
57+
if (IsHydrationNeeded(workerPath))
58+
{
59+
workerPath = GetHydratedWorkerPath(description);
60+
}
61+
5062
var arguments = new WorkerProcessArguments()
5163
{
5264
ExecutablePath = description.DefaultExecutablePath,
53-
WorkerPath = description.GetWorkerPath()
65+
WorkerPath = workerPath
5466
};
5567

5668
if (description.Language.Equals(LanguageWorkerConstants.JavaLanguageWorkerName))
@@ -133,6 +145,12 @@ internal void AddProvider(string workerDir)
133145
AddArgumentsFromAppSettings(workerDescription, languageSection);
134146

135147
string workerPath = workerDescription.GetWorkerPath();
148+
149+
if (IsHydrationNeeded(workerPath))
150+
{
151+
workerPath = GetHydratedWorkerPath(workerDescription);
152+
}
153+
136154
if (string.IsNullOrEmpty(workerPath) || File.Exists(workerPath))
137155
{
138156
_logger.LogDebug($"Will load worker provider for language: {workerDescription.Language}");
@@ -220,5 +238,41 @@ internal string GetExecutablePathForJava(string defaultExecutablePath)
220238
return Path.GetFullPath(Path.Combine(javaHome, "bin", defaultExecutablePath));
221239
}
222240
}
241+
242+
internal bool IsHydrationNeeded(string workerPath)
243+
{
244+
if (string.IsNullOrEmpty(workerPath))
245+
{
246+
return false;
247+
}
248+
249+
return workerPath.Contains(LanguageWorkerConstants.OSPlaceholder) ||
250+
workerPath.Contains(LanguageWorkerConstants.ArchitecturePlaceholder) ||
251+
workerPath.Contains(LanguageWorkerConstants.RuntimeVersionPlaceholder);
252+
}
253+
254+
internal string GetHydratedWorkerPath(WorkerDescription description)
255+
{
256+
string workerPath = description.GetWorkerPath();
257+
if (string.IsNullOrEmpty(workerPath))
258+
{
259+
return null;
260+
}
261+
262+
OSPlatform os = _systemRuntimeInformation.GetOSPlatform();
263+
264+
Architecture architecture = _systemRuntimeInformation.GetOSArchitecture();
265+
string version = _environment.GetEnvironmentVariable(LanguageWorkerConstants.FunctionWorkerRuntimeVersionSettingName);
266+
if (string.IsNullOrEmpty(version))
267+
{
268+
version = description.DefaultRuntimeVersion;
269+
}
270+
271+
description.ValidateWorkerPath(workerPath, os, architecture, version);
272+
273+
return workerPath.Replace(LanguageWorkerConstants.OSPlaceholder, os.ToString())
274+
.Replace(LanguageWorkerConstants.ArchitecturePlaceholder, architecture.ToString())
275+
.Replace(LanguageWorkerConstants.RuntimeVersionPlaceholder, version);
276+
}
223277
}
224278
}

src/WebJobs.Script/Rpc/LanguageWorkerConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public static class LanguageWorkerConstants
99
{
1010
public const int ProcessStartTimeoutSeconds = 60;
1111
public const string FunctionWorkerRuntimeSettingName = "FUNCTIONS_WORKER_RUNTIME";
12+
public const string FunctionWorkerRuntimeVersionSettingName = "FUNCTIONS_WORKER_RUNTIME_VERSION";
1213
public const string FunctionsWorkerProcessCountSettingName = "FUNCTIONS_WORKER_PROCESS_COUNT";
1314
public const string DotNetLanguageWorkerName = "dotnet";
1415
public const string NodeLanguageWorkerName = "node";
@@ -29,6 +30,9 @@ public static class LanguageWorkerConstants
2930
public const string WorkerDescriptionDefaultWorkerPath = "defaultWorkerPath";
3031
public const string WorkerDescription = "description";
3132
public const string WorkerDescriptionArguments = "arguments";
33+
public const string OSPlaceholder = "{os}";
34+
public const string ArchitecturePlaceholder = "{architecture}";
35+
public const string RuntimeVersionPlaceholder = "%FUNCTIONS_WORKER_RUNTIME_VERSION%";
3236

3337
// Profiles
3438
public const string WorkerDescriptionProfiles = "profiles";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Runtime.InteropServices;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.Rpc
7+
{
8+
public interface ISystemRuntimeInformation
9+
{
10+
Architecture GetOSArchitecture();
11+
12+
OSPlatform GetOSPlatform();
13+
}
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Runtime.InteropServices;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.Rpc
7+
{
8+
public class SystemRuntimeInformation : ISystemRuntimeInformation
9+
{
10+
public Architecture GetOSArchitecture()
11+
{
12+
return RuntimeInformation.OSArchitecture;
13+
}
14+
15+
public OSPlatform GetOSPlatform()
16+
{
17+
OSPlatform os;
18+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
19+
{
20+
os = OSPlatform.Windows;
21+
}
22+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
23+
{
24+
os = OSPlatform.OSX;
25+
}
26+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
27+
{
28+
os = OSPlatform.Linux;
29+
}
30+
31+
return os;
32+
}
33+
}
34+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using Microsoft.Azure.WebJobs.Script.Abstractions;
1010
using Microsoft.Azure.WebJobs.Script.Config;
11+
using Microsoft.Azure.WebJobs.Script.Description;
1112
using Microsoft.Azure.WebJobs.Script.Rpc;
1213
using Microsoft.Extensions.Configuration;
1314
using Microsoft.Extensions.Logging;
@@ -253,7 +254,9 @@ private IEnumerable<IWorkerProvider> TestReadWorkerProviderFromConfig(IEnumerabl
253254

254255
var scriptHostOptions = new ScriptJobHostOptions();
255256
var scriptSettingsManager = new ScriptSettingsManager(config);
256-
var configFactory = new WorkerConfigFactory(config, testLogger);
257+
var testSysRuntimeInfo = new TestSystemRuntimeInformation();
258+
var environment = new TestEnvironment();
259+
var configFactory = new WorkerConfigFactory(config, testLogger, testSysRuntimeInfo, environment);
257260
if (appSvcEnv)
258261
{
259262
var testEnvVariables = new Dictionary<string, string>

0 commit comments

Comments
 (0)