Skip to content

Commit 99581be

Browse files
authored
[in-proc backport] Support release channel settings in extension bundles resolution (#10915)
1 parent 7b95b9f commit 99581be

File tree

5 files changed

+178
-19
lines changed

5 files changed

+178
-19
lines changed

release_notes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
- Update Java Worker Version to [2.18.1](https://github.com/Azure/azure-functions-java-worker/releases/tag/2.18.1)
77
- Add support for new FeatureFlag `EnableAzureMonitorTimeIsoFormat` to enable iso time format for azmon logs for Linux Dedicated/EP Skus. (part of #7864)
88
- Update PowerShell worker to 4.0.4175 (sets defaultRuntimeVersion to 7.4 in worker.config.json)
9-
- Fixing default DateTime bug with TimeZones in TimerTrigger (#10906)
9+
- Fixing default DateTime bug with TimeZones in TimerTrigger (#10906)
10+
- Add support for the release channel setting `WEBSITE_PlatformReleaseChannel` and use this value in extension bundles resolution.

src/WebJobs.Script/Environment/EnvironmentSettingNames.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ public static class EnvironmentSettingNames
141141
public const string AntaresPlatformVersionWindows = "WEBSITE_PLATFORM_VERSION";
142142
public const string AntaresPlatformVersionLinux = "PLATFORM_VERSION";
143143

144+
// Antares Release Channel / RU Feed Settings
145+
public const string AntaresPlatformReleaseChannel = "WEBSITE_PlatformReleaseChannel";
146+
144147
// Machine identifier
145148
public const string AntaresComputerName = "COMPUTERNAME";
146149

src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.IO.Compression;
8-
using System.IO.Pipes;
98
using System.Linq;
109
using System.Net.Http;
11-
using System.Resources;
1210
using System.Threading.Tasks;
1311
using Microsoft.Azure.WebJobs.Script.Config;
1412
using Microsoft.Azure.WebJobs.Script.Configuration;
@@ -27,15 +25,17 @@ public class ExtensionBundleManager : IExtensionBundleManager
2725
private readonly FunctionsHostingConfigOptions _configOption;
2826
private readonly ILogger _logger;
2927
private readonly string _cdnUri;
28+
private readonly string _platformReleaseChannel;
3029
private string _extensionBundleVersion;
3130

3231
public ExtensionBundleManager(ExtensionBundleOptions options, IEnvironment environment, ILoggerFactory loggerFactory, FunctionsHostingConfigOptions configOption)
3332
{
3433
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
3534
_logger = loggerFactory.CreateLogger<ExtensionBundleManager>() ?? throw new ArgumentNullException(nameof(loggerFactory));
36-
_cdnUri = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ExtensionBundleSourceUri) ?? ScriptConstants.ExtensionBundleDefaultSourceUri;
3735
_options = options ?? throw new ArgumentNullException(nameof(options));
3836
_configOption = configOption ?? throw new ArgumentNullException(nameof(configOption));
37+
_cdnUri = _environment.GetEnvironmentVariable(EnvironmentSettingNames.ExtensionBundleSourceUri) ?? ScriptConstants.ExtensionBundleDefaultSourceUri;
38+
_platformReleaseChannel = _environment.GetEnvironmentVariable(EnvironmentSettingNames.AntaresPlatformReleaseChannel) ?? ScriptConstants.LatestPlatformChannelNameUpper;
3939
}
4040

4141
public async Task<ExtensionBundleDetails> GetExtensionBundleDetails()
@@ -250,7 +250,7 @@ private async Task<string> GetLatestMatchingBundleVersionAsync(HttpClient httpCl
250250
return matchingBundleVersion;
251251
}
252252

253-
internal static string FindBestVersionMatch(VersionRange versionRange, IEnumerable<string> versions, string bundleId, FunctionsHostingConfigOptions configOption)
253+
internal string FindBestVersionMatch(VersionRange versionRange, IEnumerable<string> versions, string bundleId, FunctionsHostingConfigOptions configOption)
254254
{
255255
var bundleVersions = versions.Select(p =>
256256
{
@@ -261,9 +261,9 @@ internal static string FindBestVersionMatch(VersionRange versionRange, IEnumerab
261261
version = versionRange.Satisfies(version) ? version : null;
262262
}
263263
return version;
264-
}).Where(v => v != null);
264+
}).Where(v => v != null).OrderByDescending(version => version.Version).ToList();
265265

266-
var matchingVersion = bundleVersions.OrderByDescending(version => version.Version).FirstOrDefault();
266+
var matchingVersion = ResolvePlatformReleaseChannelVersion(bundleVersions);
267267

268268
if (bundleId != ScriptConstants.DefaultExtensionBundleId)
269269
{
@@ -293,6 +293,42 @@ internal static string FindBestVersionMatch(VersionRange versionRange, IEnumerab
293293
return matchingVersion?.ToString();
294294
}
295295

296+
private NuGetVersion ResolvePlatformReleaseChannelVersion(IList<NuGetVersion> orderedByDescBundles) => _platformReleaseChannel switch
297+
{
298+
ScriptConstants.StandardPlatformChannelNameUpper or ScriptConstants.ExtendedPlatformChannelNameUpper => GetStandardOrExtendedBundleVersion(orderedByDescBundles),
299+
ScriptConstants.LatestPlatformChannelNameUpper or "" => GetLatestBundleVersion(orderedByDescBundles),
300+
_ => HandleUnknownPlatformReleaseChannelName(orderedByDescBundles)
301+
};
302+
303+
// Standard: Resolves to the version prior to the latest(n-1), if that version is available.
304+
// Extended: Resolves to the version two releases prior to the latest(n-2), if that version is available.
305+
// However, Functions and Rapid Update should treat Standard and Extended the same, resolving to n-1.
306+
private NuGetVersion GetStandardOrExtendedBundleVersion(IList<NuGetVersion> orderedByDescBundlesList)
307+
{
308+
if (orderedByDescBundlesList.Count > 1)
309+
{
310+
// These channels should resolve to the version prior to latest. This list is in descending order, which makes latest [0], and prior-to-latest [1].
311+
return orderedByDescBundlesList[1];
312+
}
313+
314+
// keep the latest version, log a notice
315+
var latest = orderedByDescBundlesList.FirstOrDefault();
316+
_logger.LogInformation("Unable to apply platform release channel configuration {platformReleaseChannelName}. Only one matching bundle version is available. {latestBundleVersion} will be used", _platformReleaseChannel, latest);
317+
return latest;
318+
}
319+
320+
private static NuGetVersion GetLatestBundleVersion(IList<NuGetVersion> orderedByDescBundlesList)
321+
{
322+
return orderedByDescBundlesList.FirstOrDefault();
323+
}
324+
325+
private NuGetVersion HandleUnknownPlatformReleaseChannelName(IList<NuGetVersion> orderedByDescBundlesList)
326+
{
327+
var latest = GetLatestBundleVersion(orderedByDescBundlesList);
328+
_logger.LogInformation("Unknown platform release channel name {platformReleaseChannelName}. The latest bundle version, {latestBundleVersion}, will be used.", _platformReleaseChannel, latest);
329+
return latest;
330+
}
331+
296332
public async Task<string> GetExtensionBundleBinPathAsync()
297333
{
298334
string bundlePath = await GetExtensionBundlePath();

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ public static class ScriptConstants
215215
public const string LogicAppDefaultExtensionBundleVersion = "[1.*, 2.0.0)";
216216
public const string DefaultExtensionBundleVersion = "[4.*, 5.0.0)";
217217

218+
// Antares Platform Channel Names, used in extension bundle resolution
219+
public const string LatestPlatformChannelNameUpper = "LATEST";
220+
public const string StandardPlatformChannelNameUpper = "STANDARD";
221+
public const string ExtendedPlatformChannelNameUpper = "EXTENDED";
222+
218223
public const string AzureMonitorTraceCategory = "FunctionAppLogs";
219224

220225
public const string KubernetesManagedAppName = "K8SE_APP_NAME";

test/WebJobs.Script.Tests/ExtensionBundle/ExtensionBundleManagerTests.cs

Lines changed: 126 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,14 @@
99
using System.Linq;
1010
using System.Net;
1111
using System.Net.Http;
12-
using System.Reflection;
1312
using System.Runtime.InteropServices;
14-
using System.Text;
1513
using System.Threading;
1614
using System.Threading.Tasks;
1715
using Microsoft.Azure.WebJobs.Script.Config;
1816
using Microsoft.Azure.WebJobs.Script.Configuration;
1917
using Microsoft.Azure.WebJobs.Script.ExtensionBundle;
20-
using Microsoft.Extensions.Configuration;
2118
using Microsoft.Extensions.Logging;
22-
using Microsoft.Extensions.Logging.Abstractions;
23-
using Microsoft.Extensions.Options;
2419
using Moq;
25-
using Newtonsoft.Json;
2620
using NuGet.Versioning;
2721
using Xunit;
2822
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
@@ -407,22 +401,122 @@ public void LimitMaxVersion(string versionRange, string hostConfigVersion, strin
407401
}
408402
}
409403

410-
var resolvedVersion = ExtensionBundleManager.FindBestVersionMatch(range, new List<string>()
411-
{ "3.7.0", "3.10.0", "3.11.0", "3.15.0", "3.14.0", "2.16.0", "3.13.0", "3.12.0", "3.9.1", "2.12.1", "2.18.0", "3.16.0", "2.19.0", "3.17.0", "4.0.2", "2.20.0", "3.18.0", "4.1.0", "4.2.0", "2.21.0", "3.19.0", "3.19.2", "4.3.0", "3.20.0" },
412-
ScriptConstants.DefaultExtensionBundleId, hostingConfiguration);
404+
var options = GetTestExtensionBundleOptions(BundleId, versionRange);
405+
var manager = GetExtensionBundleManager(options, GetTestAppServiceEnvironment());
406+
407+
var resolvedVersion = manager.FindBestVersionMatch(range, GetLargeVersionsList(), ScriptConstants.DefaultExtensionBundleId, hostingConfiguration);
408+
409+
Assert.Equal(expectedVersion, resolvedVersion);
410+
}
411+
412+
[Theory]
413+
[InlineData(ScriptConstants.LatestPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.2.0", "4.2.0")]
414+
[InlineData(ScriptConstants.StandardPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.2.0", "4.2.0")]
415+
[InlineData(ScriptConstants.ExtendedPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.2.0", "4.2.0")]
416+
[InlineData(ScriptConstants.StandardPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.3.0", "4.2.0")]
417+
[InlineData(ScriptConstants.ExtendedPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.3.0", "4.2.0")]
418+
[InlineData(ScriptConstants.StandardPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.1.0", "4.1.0")]
419+
[InlineData(ScriptConstants.ExtendedPlatformChannelNameUpper, "[4.*, 5.0.0)", "4.1.0", "4.1.0")]
420+
[InlineData(ScriptConstants.LatestPlatformChannelNameUpper, "[4.*, 5.0.0)", null, "4.3.0")]
421+
[InlineData(ScriptConstants.StandardPlatformChannelNameUpper, "[4.*, 5.0.0)", null, "4.2.0")]
422+
[InlineData(ScriptConstants.ExtendedPlatformChannelNameUpper, "[4.*, 5.0.0)", null, "4.2.0")]
423+
public void WhenPlatformReleaseChannelSet_ExpectedVersionChosen(string platformReleaseChannelName, string versionRange, string hostConfigMaxVersion, string expectedVersion)
424+
{
425+
var range = VersionRange.Parse(versionRange);
426+
var hostingConfiguration = new FunctionsHostingConfigOptions();
427+
428+
if (!string.IsNullOrEmpty(hostConfigMaxVersion))
429+
{
430+
if (range.MinVersion.Major == 3)
431+
{
432+
hostingConfiguration.Features.Add(ScriptConstants.MaximumBundleV3Version, hostConfigMaxVersion);
433+
}
434+
435+
if (range.MinVersion.Major == 4)
436+
{
437+
hostingConfiguration.Features.Add(ScriptConstants.MaximumBundleV4Version, hostConfigMaxVersion);
438+
}
439+
}
440+
441+
var options = GetTestExtensionBundleOptions(BundleId, versionRange);
442+
var testEnvironment = GetTestAppServiceEnvironment(platformReleaseChannelName);
443+
var manager = GetExtensionBundleManager(options, testEnvironment);
444+
445+
var versions = GetLargeVersionsList();
446+
447+
var resolvedVersion = manager.FindBestVersionMatch(range, versions, ScriptConstants.DefaultExtensionBundleId, hostingConfiguration);
413448

414449
Assert.Equal(expectedVersion, resolvedVersion);
415450
}
416451

417-
private ExtensionBundleManager GetExtensionBundleManager(ExtensionBundleOptions bundleOptions, TestEnvironment environment = null)
452+
[Theory]
453+
[InlineData(ScriptConstants.ExtendedPlatformChannelNameUpper)]
454+
[InlineData(ScriptConstants.StandardPlatformChannelNameUpper)]
455+
public void StandardExtendedReleaseChannel_OneBundleVersionOnDisk_Handled(string platformReleaseChannelName)
456+
{
457+
// these release channels take the version prior to the latest version
458+
// however, if there is only one bundle version available on disk, that bundle should be chosen and information logged
459+
460+
var versions = new List<string>() { "4.20.0" }; // only one bundle version available on disk
461+
var versionRange = "[4.*, 5.0.0)";
462+
var expected = "4.20.0";
463+
464+
var loggedString = $"Unable to apply platform release channel configuration {platformReleaseChannelName}. Only one matching bundle version is available. {expected} will be used";
465+
var mockLogger = GetVerifiableMockLogger(loggedString);
466+
var mockLoggerFactory = new Mock<ILoggerFactory>();
467+
mockLoggerFactory.Setup(f => f.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);
468+
469+
var options = GetTestExtensionBundleOptions(BundleId, versionRange);
470+
var testEnvironment = GetTestAppServiceEnvironment(platformReleaseChannelName);
471+
var manager = GetExtensionBundleManager(options, testEnvironment, mockLoggerFactory);
472+
473+
var resolvedVersion = manager.FindBestVersionMatch(VersionRange.Parse(versionRange), versions, ScriptConstants.DefaultExtensionBundleId, new FunctionsHostingConfigOptions());
474+
475+
Assert.Equal(expected, resolvedVersion);
476+
mockLogger.Verify();
477+
}
478+
479+
[Fact]
480+
public void UnknownReleaseChannel_ExpectedVersionChosen()
481+
{
482+
var versions = new List<string>() { "4.20.0" };
483+
var versionRange = "[4.*, 5.0.0)";
484+
var expected = "4.20.0";
485+
486+
var incorrectChannelName = "someIncorrectReleaseChannelName";
487+
var loggedString = $"Unknown platform release channel name {incorrectChannelName}. The latest bundle version, {expected}, will be used.";
488+
var mockLogger = GetVerifiableMockLogger(loggedString);
489+
var mockLoggerFactory = new Mock<ILoggerFactory>();
490+
mockLoggerFactory.Setup(f => f.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);
491+
492+
var options = GetTestExtensionBundleOptions(BundleId, versionRange);
493+
var testEnvironment = GetTestAppServiceEnvironment(incorrectChannelName);
494+
var manager = GetExtensionBundleManager(options, testEnvironment, mockLoggerFactory);
495+
496+
var resolvedVersion = manager.FindBestVersionMatch(VersionRange.Parse(versionRange), versions, ScriptConstants.DefaultExtensionBundleId, new FunctionsHostingConfigOptions());
497+
498+
Assert.Equal(expected, resolvedVersion); // unknown release channel should default to latest and log information
499+
mockLogger.Verify();
500+
}
501+
502+
private ExtensionBundleManager GetExtensionBundleManager(ExtensionBundleOptions bundleOptions, TestEnvironment environment = null, Mock<ILoggerFactory> mockLoggerFactory = null)
418503
{
419504
environment = environment ?? new TestEnvironment();
420-
return new ExtensionBundleManager(bundleOptions, environment, MockNullLoggerFactory.CreateLoggerFactory(), new FunctionsHostingConfigOptions());
505+
506+
if (mockLoggerFactory is null)
507+
{
508+
return new ExtensionBundleManager(bundleOptions, environment, MockNullLoggerFactory.CreateLoggerFactory(), new FunctionsHostingConfigOptions());
509+
}
510+
else
511+
{
512+
return new ExtensionBundleManager(bundleOptions, environment, mockLoggerFactory.Object, new FunctionsHostingConfigOptions());
513+
}
421514
}
422515

423-
private TestEnvironment GetTestAppServiceEnvironment()
516+
private TestEnvironment GetTestAppServiceEnvironment(string platformReleaseChannel = null)
424517
{
425518
var environment = new TestEnvironment();
519+
environment.SetEnvironmentVariable(AntaresPlatformReleaseChannel, platformReleaseChannel);
426520
environment.SetEnvironmentVariable(AzureWebsiteInstanceId, Guid.NewGuid().ToString("N"));
427521
string downloadPath = string.Empty;
428522
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -473,6 +567,26 @@ public void Dispose()
473567
FileUtility.Instance = null;
474568
}
475569

570+
private IList<string> GetLargeVersionsList()
571+
{
572+
return new List<string>()
573+
{ "3.7.0", "3.10.0", "3.11.0", "3.15.0", "3.14.0", "2.16.0", "3.13.0", "3.12.0", "3.9.1", "2.12.1", "2.18.0", "3.16.0", "2.19.0", "3.17.0", "4.0.2", "2.20.0", "3.18.0", "4.1.0", "4.2.0", "2.21.0", "3.19.0", "3.19.2", "4.3.0", "3.20.0" };
574+
}
575+
576+
private Mock<ILogger> GetVerifiableMockLogger(string stringToVerify)
577+
{
578+
var mockLogger = new Mock<ILogger>();
579+
mockLogger
580+
.Setup(x => x.Log(
581+
LogLevel.Information,
582+
It.IsAny<EventId>(),
583+
It.Is<It.IsAnyType>((v, t) => v.ToString().Contains(stringToVerify)),
584+
It.IsAny<Exception>(),
585+
It.IsAny<Func<It.IsAnyType, Exception, string>>()))
586+
.Verifiable();
587+
return mockLogger;
588+
}
589+
476590
private class MockHttpHandler : HttpClientHandler
477591
{
478592
private readonly string _version;

0 commit comments

Comments
 (0)