Skip to content

Commit

Permalink
Durable commands to check for non-Azure Storage backends (#3126)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgillum authored Sep 28, 2022
1 parent 33b393d commit 9a49fda
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 12 deletions.
53 changes: 48 additions & 5 deletions src/Azure.Functions.Cli/Common/DurableManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@

namespace Azure.Functions.Cli.Common
{
internal enum BackendType
{
AzureStorage,
Netherite,
MSSQL
}

internal class DurableManager : IDurableManager
{
private ISecretsManager _secretsManager;
Expand Down Expand Up @@ -47,10 +54,14 @@ internal class DurableManager : IDurableManager
public DurableManager(ISecretsManager secretsManager)
{
_secretsManager = secretsManager;
SetConnectionStringAndTaskHubName();
this.IsValid = TrySetConnectionStringAndTaskHubName();
}

private void SetConnectionStringAndTaskHubName()
public BackendType BackendType { get; private set; } = BackendType.AzureStorage;

public bool IsValid { get; private set; }

private bool TrySetConnectionStringAndTaskHubName()
{
// Set connection string key and task hub name to defaults
_connectionStringKey = DefaultConnectionStringKey;
Expand Down Expand Up @@ -85,8 +96,22 @@ private void SetConnectionStringAndTaskHubName()

if (durableTask.TryGetValue("storageProvider", StringComparison.OrdinalIgnoreCase, out JToken storageProviderToken))
{
JObject storageProviderObject = storageProviderToken as JObject;
_partitionCount = storageProviderObject?.GetValue("partitionCount", StringComparison.OrdinalIgnoreCase)?.Value<int?>();
if (storageProviderToken is JObject storageProviderObject)
{
if (storageProviderObject.TryGetValue("type", out JToken typeValue) && typeValue.Type == JTokenType.String)
{
if (Enum.TryParse(typeValue.Value<string>(), ignoreCase: true, out BackendType backendType))
{
this.BackendType = backendType;
}
}

_partitionCount = storageProviderObject?.GetValue("partitionCount", StringComparison.OrdinalIgnoreCase)?.Value<int?>();
}
else
{
throw new CliException("The host.json file contains an invalid storageProvider schema in the durableTask section.");
}
}
}
}
Expand All @@ -97,9 +122,14 @@ private void SetConnectionStringAndTaskHubName()
}
catch (Exception e)
{
ColoredConsole.WriteLine(WarningColor($"Exception thrown while attempting to parse override connection string and task hub name from '{ScriptConstants.HostMetadataFileName}':"));
// We can't throw here because that would result in an obscure error message about dependency injection in the command output.
// Instead of throwing, we write a warning and return a false value, and another part of the code will throw an exception.
ColoredConsole.WriteLine(WarningColor($"Exception thrown while attempting to parse task hub configuration from '{ScriptConstants.HostMetadataFileName}':"));
ColoredConsole.WriteLine(WarningColor(e.Message));
return false;
}

return true;
}

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

private void Initialize(out AzureStorageOrchestrationService orchestrationService, out TaskHubClient taskHubClient, string connectionStringKey = null, string taskHubName = null)
{
if (!this.IsValid)
{
throw new CliException($"The command failed due to a configuration issue. See previous error messages for details.");
}

if (this.BackendType != BackendType.AzureStorage)
{
throw new CliException(
$"The {this.BackendType} storage provider for Durable Functions is not yet supported by this command. " +
$"However, it may be supported by an SDK API or an HTTP API. " +
$"To learn about alternate ways issue commands for Durable Functions, see https://aka.ms/durable-functions-instance-management.");
}

CheckAssemblies();
SetStorageServiceAndTaskHubClient(out orchestrationService, out taskHubClient, connectionStringKey, taskHubName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>

<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\NuGet.Config" Link="NuGet.Config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
22 changes: 21 additions & 1 deletion test/Azure.Functions.Cli.Tests/E2E/DurableTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Azure.Functions.Cli.Actions.DurableActions;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Tests.E2E.Helpers;
using Newtonsoft.Json;
Expand Down Expand Up @@ -385,5 +384,26 @@ await CliTester.Run(new RunConfiguration

Environment.SetEnvironmentVariable(DurableManager.DefaultConnectionStringKey, null);
}

[Theory]
[InlineData("netherite")]
[InlineData("mssql")]
public async Task DurableAlternateBackendsNotSupportedTest(string providerType)
{
await CliTester.Run(new RunConfiguration
{
Commands = new[] { "durable get-instances" },
ExitInError = true,
ErrorContains = new[] { Enum.Parse<BackendType>(providerType, true).ToString(), "supported" },
CommandTimeout = TimeSpan.FromSeconds(10),
PreTest = (string workingDir) =>
{
string hostJsonContent = $@"{{""extensions"":{{""durableTask"":{{""storageProvider"":{{""type"":""{providerType}""}}}}}},""version"": ""2.0""}}";
File.WriteAllText(Path.Combine(workingDir, "host.json"), hostJsonContent);
}
},
_output,
startHost: false);
}
}
}
17 changes: 16 additions & 1 deletion test/Azure.Functions.Cli.Tests/E2E/Helpers/CliTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,25 @@ namespace Azure.Functions.Cli.Tests.E2E.Helpers
{
public static class CliTester
{
private static string _func = System.Environment.GetEnvironmentVariable("FUNC_PATH");
private static readonly string _func;

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

static CliTester()
{
_func = Environment.GetEnvironmentVariable("FUNC_PATH");

if (_func == null)
{
// Fallback for local testing in Visual Studio, etc.
_func = $@"{Environment.CurrentDirectory}\func.exe";
if (!File.Exists(_func))
{
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.");
}
}
}

public static Task Run(RunConfiguration runConfiguration, ITestOutputHelper output = null, string workingDir = null, bool startHost = false) => Run(new[] { runConfiguration }, output, workingDir, startHost);

public static async Task Run(RunConfiguration[] runConfigurations, ITestOutputHelper output = null, string workingDir = null, bool startHost = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void SetTaskHubName(string workingDirectoryPath, string taskHubNam
if (version?.Equals("2.0") == true)
{
// If the version is (explicitly) 2.0, prepend path to 'durableTask' with 'extensions'
hostSettings["extensions"]["durableTask"]["HubName"] = taskHubName;
hostSettings["extensions"]["durableTask"]["hubName"] = taskHubName;
}
else
{
Expand Down

0 comments on commit 9a49fda

Please sign in to comment.