Skip to content

Commit e934a1c

Browse files
authored
Merge pull request #2735 from tohling/ling-durable-synctriggers
Read storage connection in host.json for DurableFunctions
2 parents 021e7ae + 79017d9 commit e934a1c

File tree

3 files changed

+92
-23
lines changed

3 files changed

+92
-23
lines changed

schemas/json/host.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,23 @@
282282
}
283283
}
284284
}
285+
},
286+
287+
"durableTask": {
288+
"description": "Configuration settings for 'orchestration'/'activity' triggers.",
289+
"type": "object",
290+
291+
"properties": {
292+
"hubName": {
293+
"description": "The logical container for Azure Storage resources that are used for orchestrations.",
294+
"type": "string",
295+
"default": "DurableFunctionsHub"
296+
},
297+
298+
"azureStorageConnectionStringName": {
299+
"description": "An app setting (or environment variable) with the storage connection string to be used by the orchestration/activity trigger.",
300+
"type": "string"
301+
}
302+
}
285303
}
286-
}
287304
}

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

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using Microsoft.Azure.WebJobs.Script.Description;
1414
using Microsoft.Azure.WebJobs.Script.Management.Models;
1515
using Microsoft.Azure.WebJobs.Script.WebHost.Extensions;
16-
using Microsoft.Azure.WebJobs.Script.WebHost.Helpers;
1716
using Microsoft.Azure.WebJobs.Script.WebHost.Security;
1817
using Microsoft.Extensions.Logging;
1918
using Newtonsoft.Json;
@@ -23,6 +22,12 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Management
2322
{
2423
public class WebFunctionsManager : IWebFunctionsManager
2524
{
25+
private const string HubName = "HubName";
26+
private const string TaskHubName = "taskHubName";
27+
private const string Connection = "connection";
28+
private const string DurableTaskStorageConnectionName = "azureStorageConnectionStringName";
29+
private const string DurableTask = "durableTask";
30+
2631
private readonly ScriptHostConfiguration _config;
2732
private readonly ILogger _logger;
2833
private readonly HttpClient _client;
@@ -167,7 +172,7 @@ await functionMetadata
167172
/// <returns>(success, error)</returns>
168173
public async Task<(bool success, string error)> TrySyncTriggers()
169174
{
170-
var durableTaskHubName = await GetDurableTaskHubName();
175+
var durableTaskConfig = await ReadDurableTaskConfig();
171176
var functionsTriggers = (await GetFunctionsMetadata()
172177
.Select(f => f.ToFunctionTrigger(_config))
173178
.WhenAll())
@@ -176,11 +181,19 @@ await functionMetadata
176181
{
177182
// if we have a durableTask hub name and the function trigger is either orchestrationTrigger OR activityTrigger,
178183
// add a property "taskHubName" with durable task hub name.
179-
if (durableTaskHubName != null
184+
if (durableTaskConfig.Any()
180185
&& (t["type"]?.ToString().Equals("orchestrationTrigger", StringComparison.OrdinalIgnoreCase) == true
181186
|| t["type"]?.ToString().Equals("activityTrigger", StringComparison.OrdinalIgnoreCase) == true))
182187
{
183-
t["taskHubName"] = durableTaskHubName;
188+
if (durableTaskConfig.ContainsKey(HubName))
189+
{
190+
t[TaskHubName] = durableTaskConfig[HubName];
191+
}
192+
193+
if (durableTaskConfig.ContainsKey(Connection))
194+
{
195+
t[Connection] = durableTaskConfig[Connection];
196+
}
184197
}
185198
return t;
186199
});
@@ -225,22 +238,48 @@ private IEnumerable<FunctionMetadata> GetFunctionsMetadata()
225238
.ReadFunctionsMetadata(FileUtility.EnumerateDirectories(_config.RootScriptPath), _logger, new Dictionary<string, Collection<string>>(), fileSystem: FileUtility.Instance);
226239
}
227240

228-
private async Task<string> GetDurableTaskHubName()
241+
private async Task<Dictionary<string, string>> ReadDurableTaskConfig()
229242
{
230243
string hostJsonPath = Path.Combine(_config.RootScriptPath, ScriptConstants.HostMetadataFileName);
244+
var config = new Dictionary<string, string>();
231245
if (FileUtility.FileExists(hostJsonPath))
232246
{
247+
var hostJson = JObject.Parse(await FileUtility.ReadAsync(hostJsonPath));
248+
JToken durableTaskValue;
249+
250+
// we will allow case insensitivity given it is likely user hand edited
251+
// see https://github.com/Azure/azure-functions-durable-extension/issues/111
252+
//
233253
// We're looking for {VALUE}
234254
// {
235255
// "durableTask": {
236-
// "HubName": "{VALUE}"
256+
// "hubName": "{VALUE}",
257+
// "azureStorageConnectionStringName": "{VALUE}"
237258
// }
238259
// }
239-
var hostJson = JsonConvert.DeserializeObject<HostJsonModel>(await FileUtility.ReadAsync(hostJsonPath));
240-
return hostJson?.DurableTask?.HubName;
260+
if (hostJson.TryGetValue(DurableTask, StringComparison.OrdinalIgnoreCase, out durableTaskValue) && durableTaskValue != null)
261+
{
262+
try
263+
{
264+
var kvp = (JObject)durableTaskValue;
265+
if (kvp.TryGetValue(HubName, StringComparison.OrdinalIgnoreCase, out JToken nameValue) && nameValue != null)
266+
{
267+
config.Add(HubName, nameValue.ToString());
268+
}
269+
270+
if (kvp.TryGetValue(DurableTaskStorageConnectionName, StringComparison.OrdinalIgnoreCase, out nameValue) && nameValue != null)
271+
{
272+
config.Add(Connection, nameValue.ToString());
273+
}
274+
}
275+
catch (Exception)
276+
{
277+
throw new InvalidDataException("Invalid host.json configuration for 'durableTask'.");
278+
}
279+
}
241280
}
242281

243-
return null;
282+
return config;
244283
}
245284

246285
private void DeleteFunctionArtifacts(FunctionMetadataResponse function)
@@ -252,15 +291,5 @@ private void DeleteFunctionArtifacts(FunctionMetadataResponse function)
252291
FileUtility.DeleteFileSafe(testDataPath);
253292
}
254293
}
255-
256-
private class HostJsonModel
257-
{
258-
public DurableTaskHostModel DurableTask { get; set; }
259-
}
260-
261-
private class DurableTaskHostModel
262-
{
263-
public string HubName { get; set; }
264-
}
265294
}
266295
}

test/WebJobs.Script.Tests/Managment/WebFunctionsManagerTests.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public async Task VerifyDurableTaskHubNameIsAdded()
2323
{
2424
// Setup
2525
const string expectedSyncTriggersPayload = "[{\"authLevel\":\"anonymous\",\"type\":\"httpTrigger\",\"direction\":\"in\",\"name\":\"req\",\"functionName\":\"function1\"}," +
26-
"{\"name\":\"myQueueItem\",\"type\":\"orchestrationTrigger\",\"direction\":\"in\",\"queueName\":\"myqueue-items\",\"connection\":\"\",\"functionName\":\"function2\",\"taskHubName\":\"TestHubValue\"}]";
26+
"{\"name\":\"myQueueItem\",\"type\":\"orchestrationTrigger\",\"direction\":\"in\",\"queueName\":\"myqueue-items\",\"connection\":\"DurableStorage\",\"functionName\":\"function2\",\"taskHubName\":\"TestHubValue\"}," +
27+
"{\"name\":\"myQueueItem\",\"type\":\"activityTrigger\",\"direction\":\"in\",\"queueName\":\"myqueue-items\",\"connection\":\"DurableStorage\",\"functionName\":\"function3\",\"taskHubName\":\"TestHubValue\"}]";
2728
var settings = CreateWebSettings();
2829
var fileSystem = CreateFileSystem(settings.ScriptPath);
2930
var loggerFactory = MockNullLogerFactory.CreateLoggerFactory();
@@ -75,7 +76,7 @@ private static IFileSystem CreateFileSystem(string rootPath)
7576
fileSystem.SetupGet(f => f.File).Returns(fileBase.Object);
7677
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, "host.json"))).Returns(true);
7778

78-
var hostJson = new MemoryStream(Encoding.UTF8.GetBytes(@"{ ""durableTask"": { ""HubName"": ""TestHubValue"" }}"));
79+
var hostJson = new MemoryStream(Encoding.UTF8.GetBytes(@"{ ""durableTask"": { ""HubName"": ""TestHubValue"", ""azureStorageConnectionStringName"": ""DurableStorage"" }}"));
7980
hostJson.Position = 0;
8081
fileBase.Setup(f => f.Open(Path.Combine(rootPath, @"host.json"), It.IsAny<FileMode>(), It.IsAny<FileAccess>(), It.IsAny<FileShare>())).Returns(hostJson);
8182

@@ -85,7 +86,8 @@ private static IFileSystem CreateFileSystem(string rootPath)
8586
.Returns(new[]
8687
{
8788
@"x:\root\function1",
88-
@"x:\root\function2"
89+
@"x:\root\function2",
90+
@"x:\root\function3"
8991
});
9092

9193
var function1 = @"{
@@ -118,10 +120,26 @@ private static IFileSystem CreateFileSystem(string rootPath)
118120
}
119121
]
120122
}";
123+
124+
var function3 = @"{
125+
""disabled"": false,
126+
""scriptFile"": ""main.js"",
127+
""bindings"": [
128+
{
129+
""name"": ""myQueueItem"",
130+
""type"": ""activityTrigger"",
131+
""direction"": ""in"",
132+
""queueName"": ""myqueue-items"",
133+
""connection"": """"
134+
}
135+
]
136+
}";
121137
var function1Stream = new MemoryStream(Encoding.UTF8.GetBytes(function1));
122138
function1Stream.Position = 0;
123139
var function2Stream = new MemoryStream(Encoding.UTF8.GetBytes(function2));
124140
function2Stream.Position = 0;
141+
var function3Stream = new MemoryStream(Encoding.UTF8.GetBytes(function3));
142+
function3Stream.Position = 0;
125143
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, @"function1\function.json"))).Returns(true);
126144
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, @"function1\main.py"))).Returns(true);
127145
fileBase.Setup(f => f.ReadAllText(Path.Combine(rootPath, @"function1\function.json"))).Returns(function1);
@@ -132,6 +150,11 @@ private static IFileSystem CreateFileSystem(string rootPath)
132150
fileBase.Setup(f => f.ReadAllText(Path.Combine(rootPath, @"function2\function.json"))).Returns(function2);
133151
fileBase.Setup(f => f.Open(Path.Combine(rootPath, @"function2\function.json"), It.IsAny<FileMode>(), It.IsAny<FileAccess>(), It.IsAny<FileShare>())).Returns(function2Stream);
134152

153+
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, @"function3\function.json"))).Returns(true);
154+
fileBase.Setup(f => f.Exists(Path.Combine(rootPath, @"function3\main.js"))).Returns(true);
155+
fileBase.Setup(f => f.ReadAllText(Path.Combine(rootPath, @"function3\function.json"))).Returns(function3);
156+
fileBase.Setup(f => f.Open(Path.Combine(rootPath, @"function3\function.json"), It.IsAny<FileMode>(), It.IsAny<FileAccess>(), It.IsAny<FileShare>())).Returns(function3Stream);
157+
135158
return fileSystem.Object;
136159
}
137160

0 commit comments

Comments
 (0)