Skip to content

Commit 8856181

Browse files
authored
Durable commands to check for non-Azure Storage backends (#3161)
Cherry-pick of #3126 in the v4 branch
1 parent 00fcf76 commit 8856181

File tree

5 files changed

+87
-13
lines changed

5 files changed

+87
-13
lines changed

src/Azure.Functions.Cli/Common/DurableManager.cs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818

1919
namespace Azure.Functions.Cli.Common
2020
{
21+
internal enum BackendType
22+
{
23+
AzureStorage,
24+
Netherite,
25+
MSSQL
26+
}
27+
2128
internal class DurableManager : IDurableManager
2229
{
2330
private ISecretsManager _secretsManager;
@@ -47,10 +54,14 @@ internal class DurableManager : IDurableManager
4754
public DurableManager(ISecretsManager secretsManager)
4855
{
4956
_secretsManager = secretsManager;
50-
SetConnectionStringAndTaskHubName();
57+
this.IsValid = TrySetConnectionStringAndTaskHubName();
5158
}
5259

53-
private void SetConnectionStringAndTaskHubName()
60+
public BackendType BackendType { get; private set; } = BackendType.AzureStorage;
61+
62+
public bool IsValid { get; private set; }
63+
64+
private bool TrySetConnectionStringAndTaskHubName()
5465
{
5566
// Set connection string key and task hub name to defaults
5667
_connectionStringKey = DefaultConnectionStringKey;
@@ -85,8 +96,22 @@ private void SetConnectionStringAndTaskHubName()
8596

8697
if (durableTask.TryGetValue("storageProvider", StringComparison.OrdinalIgnoreCase, out JToken storageProviderToken))
8798
{
88-
JObject storageProviderObject = storageProviderToken as JObject;
89-
_partitionCount = storageProviderObject?.GetValue("partitionCount", StringComparison.OrdinalIgnoreCase)?.Value<int?>();
99+
if (storageProviderToken is JObject storageProviderObject)
100+
{
101+
if (storageProviderObject.TryGetValue("type", out JToken typeValue) && typeValue.Type == JTokenType.String)
102+
{
103+
if (Enum.TryParse(typeValue.Value<string>(), ignoreCase: true, out BackendType backendType))
104+
{
105+
this.BackendType = backendType;
106+
}
107+
}
108+
109+
_partitionCount = storageProviderObject?.GetValue("partitionCount", StringComparison.OrdinalIgnoreCase)?.Value<int?>();
110+
}
111+
else
112+
{
113+
throw new CliException("The host.json file contains an invalid storageProvider schema in the durableTask section.");
114+
}
90115
}
91116
}
92117
}
@@ -97,9 +122,14 @@ private void SetConnectionStringAndTaskHubName()
97122
}
98123
catch (Exception e)
99124
{
100-
ColoredConsole.WriteLine(WarningColor($"Exception thrown while attempting to parse override connection string and task hub name from '{ScriptConstants.HostMetadataFileName}':"));
125+
// We can't throw here because that would result in an obscure error message about dependency injection in the command output.
126+
// Instead of throwing, we write a warning and return a false value, and another part of the code will throw an exception.
127+
ColoredConsole.WriteLine(WarningColor($"Exception thrown while attempting to parse task hub configuration from '{ScriptConstants.HostMetadataFileName}':"));
101128
ColoredConsole.WriteLine(WarningColor(e.Message));
129+
return false;
102130
}
131+
132+
return true;
103133
}
104134

105135
private void SetStorageServiceAndTaskHubClient(out AzureStorageOrchestrationService orchestrationService, out TaskHubClient taskHubClient, string connectionStringKey = null, string taskHubName = null)
@@ -134,6 +164,19 @@ private void SetStorageServiceAndTaskHubClient(out AzureStorageOrchestrationServ
134164

135165
private void Initialize(out AzureStorageOrchestrationService orchestrationService, out TaskHubClient taskHubClient, string connectionStringKey = null, string taskHubName = null)
136166
{
167+
if (!this.IsValid)
168+
{
169+
throw new CliException($"The command failed due to a configuration issue. See previous error messages for details.");
170+
}
171+
172+
if (this.BackendType != BackendType.AzureStorage)
173+
{
174+
throw new CliException(
175+
$"The {this.BackendType} storage provider for Durable Functions is not yet supported by this command. " +
176+
$"However, it may be supported by an SDK API or an HTTP API. " +
177+
$"To learn about alternate ways issue commands for Durable Functions, see https://aka.ms/durable-functions-instance-management.");
178+
}
179+
137180
CheckAssemblies();
138181
SetStorageServiceAndTaskHubClient(out orchestrationService, out taskHubClient, connectionStringKey, taskHubName);
139182
}

test/Azure.Functions.Cli.Tests/Azure.Functions.Cli.Tests.csproj

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@
3636
</ItemGroup>
3737

3838
<ItemGroup>
39-
<Folder Include="Properties\" />
40-
</ItemGroup>
41-
42-
<ItemGroup>
43-
<None Include="..\..\.nuget\NuGet.Config" Link="NuGet.Config">
39+
<None Include="..\..\NuGet.Config" Link="NuGet.Config">
4440
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
4541
</None>
4642
</ItemGroup>

test/Azure.Functions.Cli.Tests/E2E/DurableTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.IO;
33
using System.Threading.Tasks;
4-
using Azure.Functions.Cli.Actions.DurableActions;
54
using Azure.Functions.Cli.Common;
65
using Azure.Functions.Cli.Tests.E2E.Helpers;
76
using Newtonsoft.Json;
@@ -385,5 +384,26 @@ await CliTester.Run(new RunConfiguration
385384

386385
Environment.SetEnvironmentVariable(DurableManager.DefaultConnectionStringKey, null);
387386
}
387+
388+
[Theory]
389+
[InlineData("netherite")]
390+
[InlineData("mssql")]
391+
public async Task DurableAlternateBackendsNotSupportedTest(string providerType)
392+
{
393+
await CliTester.Run(new RunConfiguration
394+
{
395+
Commands = new[] { "durable get-instances" },
396+
ExitInError = true,
397+
ErrorContains = new[] { Enum.Parse<BackendType>(providerType, true).ToString(), "supported" },
398+
CommandTimeout = TimeSpan.FromSeconds(10),
399+
PreTest = (string workingDir) =>
400+
{
401+
string hostJsonContent = $@"{{""extensions"":{{""durableTask"":{{""storageProvider"":{{""type"":""{providerType}""}}}}}},""version"": ""2.0""}}";
402+
File.WriteAllText(Path.Combine(workingDir, "host.json"), hostJsonContent);
403+
}
404+
},
405+
_output,
406+
startHost: false);
407+
}
388408
}
389409
}

test/Azure.Functions.Cli.Tests/E2E/Helpers/CliTester.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,25 @@ namespace Azure.Functions.Cli.Tests.E2E.Helpers
1111
{
1212
public static class CliTester
1313
{
14-
private static string _func = System.Environment.GetEnvironmentVariable("FUNC_PATH");
14+
private static readonly string _func;
1515

1616
private const string StartHostCommand = "start --build";
1717

18+
static CliTester()
19+
{
20+
_func = Environment.GetEnvironmentVariable("FUNC_PATH");
21+
22+
if (_func == null)
23+
{
24+
// Fallback for local testing in Visual Studio, etc.
25+
_func = $@"{Environment.CurrentDirectory}\func.exe";
26+
if (!File.Exists(_func))
27+
{
28+
throw new ApplicationException("Could not locate the func.exe to use for testing. Make sure the FUNC_PATH environment variable is set to the full path of the func executable.");
29+
}
30+
}
31+
}
32+
1833
public static Task Run(RunConfiguration runConfiguration, ITestOutputHelper output = null, string workingDir = null, bool startHost = false) => Run(new[] { runConfiguration }, output, workingDir, startHost);
1934

2035
public static async Task Run(RunConfiguration[] runConfigurations, ITestOutputHelper output = null, string workingDir = null, bool startHost = false)

test/Azure.Functions.Cli.Tests/E2E/Helpers/DurableHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static void SetTaskHubName(string workingDirectoryPath, string taskHubNam
2222
if (version?.Equals("2.0") == true)
2323
{
2424
// If the version is (explicitly) 2.0, prepend path to 'durableTask' with 'extensions'
25-
hostSettings["extensions"]["durableTask"]["HubName"] = taskHubName;
25+
hostSettings["extensions"]["durableTask"]["hubName"] = taskHubName;
2626
}
2727
else
2828
{

0 commit comments

Comments
 (0)