Skip to content

Commit ffa0779

Browse files
authored
Patch Durable Extension version check (#9331)
1 parent f95cea5 commit ffa0779

File tree

6 files changed

+74
-36
lines changed

6 files changed

+74
-36
lines changed

release_notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
<!-- Please add your release notes in the following format:
44
- My change description (#PR)
55
-->
6+
7+
- Patch Durable Functions Extension version check (https://github.com/Azure/azure-functions-host/pull/9331)
68
- Update Java Worker Version to [2.12.0](https://github.com/Azure/azure-functions-java-worker/releases/tag/2.12.0)
79
- Update Python Worker Version to [4.14.0](https://github.com/Azure/azure-functions-python-worker/releases/tag/4.14.0)
810
- Update protobuf to [v1.9.0-protofile](https://github.com/Azure/azure-functions-language-worker-protobuf/releases/tag/v1.9.0-protofile)

src/WebJobs.Script.WebHost/Management/FunctionsSyncManager.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
using Azure.Storage.Blobs;
1515
using Microsoft.Azure.WebJobs.Host.Executors;
1616
using Microsoft.Azure.WebJobs.Host.Storage;
17+
using Microsoft.Azure.WebJobs.Script.Config;
18+
using Microsoft.Azure.WebJobs.Script.DependencyInjection;
1719
using Microsoft.Azure.WebJobs.Script.Description;
1820
using Microsoft.Azure.WebJobs.Script.Models;
1921
using Microsoft.Azure.WebJobs.Script.WebHost.Extensions;
@@ -447,6 +449,7 @@ internal async Task<IEnumerable<JObject>> GetFunctionTriggers(IEnumerable<Functi
447449
.WhenAll())
448450
.Where(t => t != null);
449451

452+
// TODO: We should remove extension-specific logic from the Host. See: https://github.com/Azure/azure-functions-host/issues/5390
450453
if (triggers.Any(IsDurableTrigger))
451454
{
452455
DurableConfig durableTaskConfig = await ReadDurableTaskConfig();
@@ -545,19 +548,39 @@ private async Task<DurableConfig> ReadDurableTaskConfig()
545548
// This is a stopgap approach to get the Durable extension version. It duplicates some logic in ExtensionManager.cs.
546549
private async Task<string> GetDurableMajorVersionAsync(JObject hostJson, ScriptJobHostOptions hostOptions)
547550
{
551+
string metadataFilePath;
548552
bool isUsingBundles = hostJson != null && hostJson.TryGetValue("extensionBundle", StringComparison.OrdinalIgnoreCase, out _);
549-
if (isUsingBundles)
553+
// This feature flag controls whether to opt out of the SyncTrigger metadata fix for OOProc DF apps from https://github.com/Azure/azure-functions-host/pull/9331
554+
if (FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableLegacyDurableVersionCheck))
550555
{
551-
// TODO: As of 2019-12-12, there are no extension bundles for version 2.x of Durable.
552-
// This may change in the future.
553-
return "1";
554-
}
556+
// using legacy behavior, which concludes that out of process DF apps (including .NET isolated) are using DF Extension V1.x
557+
// as a result, the SyncTriggers payload for these apps will be missing some metadata like "taskHubName"
558+
if (isUsingBundles)
559+
{
560+
return "1";
561+
}
555562

556-
string binPath = binPath = Path.Combine(hostOptions.RootScriptPath, "bin");
557-
string metadataFilePath = Path.Combine(binPath, ScriptConstants.ExtensionsMetadataFileName);
558-
if (!FileUtility.FileExists(metadataFilePath))
563+
string binPath = binPath = Path.Combine(hostOptions.RootScriptPath, "bin");
564+
metadataFilePath = Path.Combine(binPath, ScriptConstants.ExtensionsMetadataFileName);
565+
if (!FileUtility.FileExists(metadataFilePath))
566+
{
567+
return null;
568+
}
569+
}
570+
else
559571
{
560-
return null;
572+
if (isUsingBundles)
573+
{
574+
// From Functions runtime V4 onwards, only bundles >= V2.x is supported, which implies the app should be using DF V2 or greater.
575+
return "2";
576+
}
577+
578+
// If the app is not using bundles, we look for extensions.json
579+
if (!Utility.TryResolveExtensionsMetadataPath(hostOptions.RootScriptPath, out string metadataDirectoryPath, out _))
580+
{
581+
return null;
582+
}
583+
metadataFilePath = Path.Combine(metadataDirectoryPath, ScriptConstants.ExtensionsMetadataFileName);
561584
}
562585

563586
var extensionMetadata = JObject.Parse(await FileUtility.ReadAsync(metadataFilePath));

src/WebJobs.Script/DependencyInjection/ScriptStartupTypeLocator.cs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -126,33 +126,10 @@ public async Task<IEnumerable<Type>> GetExtensionsStartupTypesAsync()
126126
else
127127
{
128128
extensionsMetadataPath = Path.Combine(_rootScriptPath, "bin");
129-
130-
// Verify if the file exists and apply fallback paths
131-
// The fallback order is:
132-
// 1 - Script root
133-
// - If the system folder exists with metadata file at the root, use that as the base probing path
134-
// 2 - System folder
135-
if (!File.Exists(Path.Combine(extensionsMetadataPath, ScriptConstants.ExtensionsMetadataFileName)))
129+
if (Utility.TryResolveExtensionsMetadataPath(_rootScriptPath, out string resolvedPath, out baseProbingPath))
136130
{
137-
string systemPath = Path.Combine(_rootScriptPath, ScriptConstants.AzureFunctionsSystemDirectoryName);
138-
139-
if (File.Exists(Path.Combine(_rootScriptPath, ScriptConstants.ExtensionsMetadataFileName)))
140-
{
141-
// As a fallback, allow extensions.json in the root path.
142-
extensionsMetadataPath = _rootScriptPath;
143-
144-
// If the system path exists, that should take precedence as the base probing path
145-
if (Directory.Exists(systemPath))
146-
{
147-
baseProbingPath = systemPath;
148-
}
149-
}
150-
else if (File.Exists(Path.Combine(systemPath, ScriptConstants.ExtensionsMetadataFileName)))
151-
{
152-
extensionsMetadataPath = systemPath;
153-
}
131+
extensionsMetadataPath = resolvedPath;
154132
}
155-
156133
_logger.ScriptStartNotLoadingExtensionBundle(extensionsMetadataPath, bundleConfigured, isPrecompiledFunctionApp, isLegacyExtensionBundle);
157134
}
158135

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public static class ScriptConstants
129129
public const string FeatureFlagEnableHttpProxying = "EnableHttpProxying";
130130
public const string HostingConfigDisableLinuxAppServiceDetailedExecutionEvents = "DisableLinuxExecutionDetails";
131131
public const string HostingConfigDisableLinuxAppServiceExecutionEventLogBackoff = "DisableLinuxLogBackoff";
132+
public const string FeatureFlagEnableLegacyDurableVersionCheck = "EnableLegacyDurableVersionCheck";
132133

133134
public const string SiteAzureFunctionsUriFormat = "https://{0}.azurewebsites.net/azurefunctions";
134135
public const string ScmSiteUriFormat = "https://{0}.scm.azurewebsites.net";

src/WebJobs.Script/Utility.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,41 @@ public static void ExecuteAfterColdStartDelay(IEnvironment environment, Action t
782782
}
783783
}
784784

785+
public static bool TryResolveExtensionsMetadataPath(string rootScriptPath, out string extensionsMetadataPath, out string baseProbingPath)
786+
{
787+
baseProbingPath = null;
788+
789+
// Verify if the file exists and apply fallback paths
790+
// The fallback order is:
791+
// 1 - Script root
792+
// - If the system folder exists with metadata file at the root, use that as the base probing path
793+
// 2 - System folder
794+
extensionsMetadataPath = Path.Combine(rootScriptPath, "bin");
795+
if (!FileUtility.FileExists(Path.Combine(extensionsMetadataPath, ScriptConstants.ExtensionsMetadataFileName)))
796+
{
797+
extensionsMetadataPath = null;
798+
string systemPath = Path.Combine(rootScriptPath, ScriptConstants.AzureFunctionsSystemDirectoryName);
799+
800+
if (FileUtility.FileExists(Path.Combine(rootScriptPath, ScriptConstants.ExtensionsMetadataFileName)))
801+
{
802+
// As a fallback, allow extensions.json in the root path.
803+
extensionsMetadataPath = rootScriptPath;
804+
805+
// If the system path exists, that should take precedence as the base probing path
806+
if (Directory.Exists(systemPath))
807+
{
808+
baseProbingPath = systemPath;
809+
}
810+
}
811+
else if (FileUtility.FileExists(Path.Combine(systemPath, ScriptConstants.ExtensionsMetadataFileName)))
812+
{
813+
extensionsMetadataPath = systemPath;
814+
}
815+
}
816+
bool foundMetadata = !string.IsNullOrEmpty(extensionsMetadataPath);
817+
return foundMetadata;
818+
}
819+
785820
public static bool TryCleanUrl(string url, out string cleaned)
786821
{
787822
cleaned = null;

test/WebJobs.Script.Tests.Integration/Management/FunctionsSyncManagerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ public async Task TrySyncTriggers_BackgroundSync_SetTriggersFailure_HashNotUpdat
454454
}
455455

456456
[Fact]
457-
public async Task TrySyncTriggers_NoDurableTaskHub_UsesBundles_V1DefaultsPosted()
457+
public async Task TrySyncTriggers_NoDurableTaskHub_UsesBundles_V2DefaultsPosted()
458458
{
459459
_mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName)).Returns("TestHubValue");
460460

@@ -474,7 +474,7 @@ public async Task TrySyncTriggers_NoDurableTaskHub_UsesBundles_V1DefaultsPosted(
474474
Assert.True(syncResult.Success, "SyncTriggers should return success true");
475475
Assert.True(string.IsNullOrEmpty(syncResult.Error), "Error should be null or empty");
476476

477-
VerifyResultWithCacheOn(expectedTaskHub: null, connection: null);
477+
VerifyResultWithCacheOn(expectedTaskHub: "TestHubValue", connection: null);
478478
}
479479

480480
}

0 commit comments

Comments
 (0)