From e034e29ce14a86f47f94904733496c43d8c17990 Mon Sep 17 00:00:00 2001 From: andystaples <77818326+andystaples@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:27:02 -0600 Subject: [PATCH 1/2] Shut off MD module upgrades after 7.4 EOL (#1084) * Shut off MD module upgrades after 7.4 EOL * Add instructions for new PowerShell versions --- create_new_worker_instructions.md | 6 +++ .../BackgroundDependencySnapshotMaintainer.cs | 24 ++++++++- src/DependencyManagement/DependencyManager.cs | 18 ++++++- src/DependencyManagement/WorkerEnvironment.cs | 7 +++ src/resources/PowerShellWorkerStrings.resx | 9 ++-- ...groundDependencySnapshotMaintainerTests.cs | 50 ++++++++++++++++--- 6 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 create_new_worker_instructions.md diff --git a/create_new_worker_instructions.md b/create_new_worker_instructions.md new file mode 100644 index 00000000..b3824a12 --- /dev/null +++ b/create_new_worker_instructions.md @@ -0,0 +1,6 @@ +## Instructions for Upgrading the PowerShell Language Worker to a New PowerShell SDK Minor Version (7.6+) +Once a new PowerShell SDK version is released on [GiHub](https://github.com/PowerShell/PowerShell/releases), follow these steps to upgrade the PowerShell SDK reference used by the language worker: + +- Update the solution targets as needed for whatever .NET version is targeted by the new PowerShell +- Follow instructions in upgrade_ps_sdk_instructions.md to update loosely linked dependencies in project files +- Update the Managed Dependency shutoff date in src/DependencyManagement/WorkerEnvironment.cs diff --git a/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs b/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs index deba2e90..642ac624 100644 --- a/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs +++ b/src/DependencyManagement/BackgroundDependencySnapshotMaintainer.cs @@ -20,6 +20,8 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna private TimeSpan MaxBackgroundUpgradePeriod { get; } = PowerShellWorkerConfiguration.GetTimeSpan("MDMaxBackgroundUpgradePeriod") ?? TimeSpan.FromDays(7); + private Func _getShouldPerformManagedDependencyUpgrades; + private readonly IDependencyManagerStorage _storage; private readonly IDependencySnapshotInstaller _installer; private readonly IDependencySnapshotPurger _purger; @@ -29,11 +31,14 @@ internal class BackgroundDependencySnapshotMaintainer : IBackgroundDependencySna public BackgroundDependencySnapshotMaintainer( IDependencyManagerStorage storage, IDependencySnapshotInstaller installer, - IDependencySnapshotPurger purger) + IDependencySnapshotPurger purger, + Func getShouldPerformManagedDependencyUpgrades) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _installer = installer ?? throw new ArgumentNullException(nameof(installer)); _purger = purger ?? throw new ArgumentNullException(nameof(purger)); + _getShouldPerformManagedDependencyUpgrades = getShouldPerformManagedDependencyUpgrades; + } public void Start(string currentSnapshotPath, DependencyManifestEntry[] dependencyManifest, ILogger logger) @@ -56,6 +61,23 @@ public string InstallAndPurgeSnapshots(Func pwshFactory, ILogger log { try { + if (!_getShouldPerformManagedDependencyUpgrades()) + { + logger.Log( + isUserOnlyLog: false, + RpcLog.Types.Level.Warning, + PowerShellWorkerStrings.AutomaticUpgradesAreDisabled); + + // Shutdown the timer that calls this method after the EOL date + if (_installAndPurgeTimer is not null) + { + _installAndPurgeTimer.Dispose(); + _installAndPurgeTimer = null; + } + + return null; + } + // Purge before installing a new snapshot, as we may be able to free some space. _purger.Purge(logger); diff --git a/src/DependencyManagement/DependencyManager.cs b/src/DependencyManagement/DependencyManager.cs index 71caf2bd..a2bbe57f 100644 --- a/src/DependencyManagement/DependencyManager.cs +++ b/src/DependencyManagement/DependencyManager.cs @@ -41,6 +41,9 @@ internal class DependencyManager : IDisposable private Task _dependencyInstallationTask; + private bool EnableAutomaticUpgrades { get; } = + PowerShellWorkerConfiguration.GetBoolean("MDEnableAutomaticUpgrades") ?? false; + #endregion public DependencyManager( @@ -67,7 +70,8 @@ public DependencyManager( maintainer ?? new BackgroundDependencySnapshotMaintainer( _storage, _installer, - new DependencySnapshotPurger(_storage)); + new DependencySnapshotPurger(_storage), + ShouldEnableManagedDpendencyUpgrades); _currentSnapshotContentLogger = currentSnapshotContentLogger ?? new BackgroundDependencySnapshotContentLogger(snapshotContentLogger); } @@ -126,6 +130,18 @@ internal string Initialize(ILogger logger) } } + /// + /// Determines whether the function app should enable automatic upgrades for managed dependencies + /// + /// + /// True if Managed Dependencies should be upgraded (SDK is not past it's deprecation date OR user has configured this behavior via MDEnableAutomaticUpgrades env var + /// False if Managed Dependencies should not be upgraded + /// + private bool ShouldEnableManagedDpendencyUpgrades() + { + return !WorkerEnvironment.IsPowerShellSDKDeprecated() || EnableAutomaticUpgrades; + } + /// /// Start dependency installation if needed. /// firstPowerShell is the first PowerShell instance created in this process (which this is important for local debugging), diff --git a/src/DependencyManagement/WorkerEnvironment.cs b/src/DependencyManagement/WorkerEnvironment.cs index ba6cbe9e..2e59242a 100644 --- a/src/DependencyManagement/WorkerEnvironment.cs +++ b/src/DependencyManagement/WorkerEnvironment.cs @@ -16,6 +16,8 @@ internal static class WorkerEnvironment private const string ContainerName = "CONTAINER_NAME"; private const string LegionServiceHost = "LEGION_SERVICE_HOST"; + private static readonly DateTime PowerShellSDKDeprecationDate = new DateTime(2026, 11, 10); + public static bool IsAppService() { return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(AzureWebsiteInstanceId)); @@ -32,5 +34,10 @@ public static bool IsLinuxConsumptionOnLegion() !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ContainerName)) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LegionServiceHost)); } + + public static bool IsPowerShellSDKDeprecated() + { + return DateTime.Now > PowerShellSDKDeprecationDate; + } } } diff --git a/src/resources/PowerShellWorkerStrings.resx b/src/resources/PowerShellWorkerStrings.resx index 53f0c801..d801396c 100644 --- a/src/resources/PowerShellWorkerStrings.resx +++ b/src/resources/PowerShellWorkerStrings.resx @@ -352,6 +352,9 @@ Dependency snapshot '{0}' does not contain acceptable module versions. + + Worker init request completed in {0} ms. + Found external Durable Functions SDK in session: Name='{0}', Version='{1}', Path='{2}'. @@ -361,9 +364,6 @@ Unable to initialize orchestrator function due to presence of other bindings. Total number of bindings found is '{0}'. Orchestrator Functions should never use any input or output bindings other than the orchestration trigger itself. See: aka.ms/df-bindings - - Worker init request completed in {0} ms. - Operation '{0}' expected '{1}' result(s) but received '{2}'. @@ -388,4 +388,7 @@ The app is configured to use OpenTelemetry but the TraceContext passed from host was null. + + Automatic upgrades are disabled in PowerShell 7.4 function apps. This warning should not be emitted until PowerShell 7.4's End of Life date, at which time, more guidance will be available regarding how to upgrade your function app to the latest version. + \ No newline at end of file diff --git a/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs b/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs index aac937d9..b4a2619f 100644 --- a/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs +++ b/test/Unit/DependencyManagement/BackgroundDependencySnapshotMaintainerTests.cs @@ -13,8 +13,8 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Test.DependencyManagement using Microsoft.Azure.Functions.PowerShellWorker.DependencyManagement; using Microsoft.Azure.Functions.PowerShellWorker.Utility; - using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level; + using Microsoft.Azure.WebJobs.Script.Grpc.Messages; public class BackgroundDependencySnapshotMaintainerTests { @@ -30,7 +30,7 @@ public class BackgroundDependencySnapshotMaintainerTests [Fact] public void SetsCurrentlyUsedSnapshotOnPurger() { - using (var maintainer = CreateMaintainerWithMocks()) + using (var maintainer = CreateMaintainerWithMocks(true)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); } @@ -56,7 +56,7 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound() It.IsAny())); using (var dummyPowerShell = PowerShell.Create()) - using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod)) + using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); @@ -73,6 +73,41 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound() } } + [Fact] + public void DoesNothingIfManagedDependenciesUpgradesAreDisabled() + { + _mockStorage.Setup(_ => _.GetInstalledAndInstallingSnapshots()).Returns(new[] { "older snapshot" }); + _mockStorage.Setup(_ => _.GetSnapshotCreationTimeUtc("older snapshot")) + .Returns(DateTime.UtcNow - _minBackgroundUpgradePeriod - TimeSpan.FromSeconds(1)); + + _mockStorage.Setup(_ => _.CreateNewSnapshotPath()).Returns("new snapshot path"); + + _mockInstaller.Setup( + _ => _.InstallSnapshot( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())); + + using (var dummyPowerShell = PowerShell.Create()) + using (var maintainer = CreateMaintainerWithMocks(false, _minBackgroundUpgradePeriod)) + { + maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); + + // ReSharper disable once AccessToDisposedClosure + var installedSnapshotPath = maintainer.InstallAndPurgeSnapshots(() => dummyPowerShell, _mockLogger.Object); + Assert.Equal(null, installedSnapshotPath); + + // ReSharper disable once AccessToDisposedClosure + _mockInstaller.Verify( + _ => _.InstallSnapshot(_dependencyManifest, "new snapshot path", dummyPowerShell, DependencySnapshotInstallationMode.Optional, _mockLogger.Object), + Times.Never); + + _mockLogger.Verify(_ => _.Log(false, LogLevel.Warning, PowerShellWorkerStrings.AutomaticUpgradesAreDisabled, null), Times.Once); + } + } + [Fact] public void DoesNotInstallSnapshotIfRecentlyInstalledSnapshotFound() { @@ -80,7 +115,7 @@ public void DoesNotInstallSnapshotIfRecentlyInstalledSnapshotFound() _mockStorage.Setup(_ => _.GetSnapshotCreationTimeUtc("older snapshot")) .Returns(DateTime.UtcNow - _minBackgroundUpgradePeriod + TimeSpan.FromSeconds(1)); - using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod)) + using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); @@ -112,7 +147,7 @@ public void LogsWarningIfCannotInstallSnapshot() .Throws(injectedException); using (var dummyPowerShell = PowerShell.Create()) - using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod)) + using (var maintainer = CreateMaintainerWithMocks(true, _minBackgroundUpgradePeriod)) { maintainer.Start("current snapshot", _dependencyManifest, _mockLogger.Object); @@ -129,12 +164,13 @@ public void LogsWarningIfCannotInstallSnapshot() Times.Once); } - private BackgroundDependencySnapshotMaintainer CreateMaintainerWithMocks(TimeSpan? minBackgroundUpgradePeriod = null) + private BackgroundDependencySnapshotMaintainer CreateMaintainerWithMocks(bool shouldPerformManagedDependenciesUpgrades, TimeSpan? minBackgroundUpgradePeriod = null) { var maintainer = new BackgroundDependencySnapshotMaintainer( _mockStorage.Object, _mockInstaller.Object, - _mockPurger.Object); + _mockPurger.Object, + () => { return shouldPerformManagedDependenciesUpgrades; }); if (minBackgroundUpgradePeriod != null) { From 82b9ad512fe702179e4fd906bf00fc24bea51a94 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Wed, 25 Sep 2024 15:35:35 -0600 Subject: [PATCH 2/2] 7.2 specific changes --- src/DependencyManagement/WorkerEnvironment.cs | 2 +- src/resources/PowerShellWorkerStrings.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyManagement/WorkerEnvironment.cs b/src/DependencyManagement/WorkerEnvironment.cs index 2e59242a..fedeaaed 100644 --- a/src/DependencyManagement/WorkerEnvironment.cs +++ b/src/DependencyManagement/WorkerEnvironment.cs @@ -16,7 +16,7 @@ internal static class WorkerEnvironment private const string ContainerName = "CONTAINER_NAME"; private const string LegionServiceHost = "LEGION_SERVICE_HOST"; - private static readonly DateTime PowerShellSDKDeprecationDate = new DateTime(2026, 11, 10); + private static readonly DateTime PowerShellSDKDeprecationDate = new DateTime(2024, 11, 8); public static bool IsAppService() { diff --git a/src/resources/PowerShellWorkerStrings.resx b/src/resources/PowerShellWorkerStrings.resx index d801396c..3c83bbab 100644 --- a/src/resources/PowerShellWorkerStrings.resx +++ b/src/resources/PowerShellWorkerStrings.resx @@ -389,6 +389,6 @@ The app is configured to use OpenTelemetry but the TraceContext passed from host was null. - Automatic upgrades are disabled in PowerShell 7.4 function apps. This warning should not be emitted until PowerShell 7.4's End of Life date, at which time, more guidance will be available regarding how to upgrade your function app to the latest version. + Automatic upgrades are disabled in PowerShell 7.2 function apps. This warning should not be emitted until PowerShell 7.2's End of Life date. For more information, please see https://azure.microsoft.com/en-us/updates/v2/powershell72-azure-functions-retirement \ No newline at end of file