Skip to content

Commit 31f941b

Browse files
committed
Fix various DF bugs (#783)
1 parent 6dd32c3 commit 31f941b

File tree

11 files changed

+207
-6
lines changed

11 files changed

+207
-6
lines changed

release_notes.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
* Bug fix: Activity Functions can now use output bindings (https://github.com/Azure/azure-functions-powershell-worker/issues/646)
1+
* Bug fix: [Context.InstanceId can now be accessed](https://github.com/Azure/azure-functions-powershell-worker/issues/727)
2+
* Bug fix: [Data in External Events is now read and returned to orchestrator](https://github.com/Azure/azure-functions-powershell-worker/issues/68)

src/Durable/DurableTaskHandler.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public void StopAndInitiateDurableTaskOrReplay(
4747
}
4848

4949
completedHistoryEvent.IsProcessed = true;
50+
context.IsReplaying = completedHistoryEvent.IsPlayed;
5051

5152
switch (completedHistoryEvent.EventType)
5253
{
@@ -57,7 +58,13 @@ public void StopAndInitiateDurableTaskOrReplay(
5758
output(eventResult);
5859
}
5960
break;
60-
61+
case HistoryEventType.EventRaised:
62+
var eventRaisedResult = GetEventResult(completedHistoryEvent);
63+
if (eventRaisedResult != null)
64+
{
65+
output(eventRaisedResult);
66+
}
67+
break;
6168
case HistoryEventType.TaskFailed:
6269
if (retryOptions == null)
6370
{
@@ -126,6 +133,7 @@ public void WaitAll(
126133
var allTasksCompleted = completedEvents.Count == tasksToWaitFor.Count;
127134
if (allTasksCompleted)
128135
{
136+
context.IsReplaying = completedEvents.Count == 0 ? false : completedEvents[0].IsPlayed;
129137
CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context);
130138

131139
foreach (var completedHistoryEvent in completedEvents)
@@ -164,6 +172,7 @@ public void WaitAny(
164172
if (scheduledHistoryEvent != null)
165173
{
166174
scheduledHistoryEvent.IsProcessed = true;
175+
scheduledHistoryEvent.IsPlayed = true;
167176
}
168177

169178
if (completedHistoryEvent != null)
@@ -179,12 +188,14 @@ public void WaitAny(
179188
}
180189

181190
completedHistoryEvent.IsProcessed = true;
191+
completedHistoryEvent.IsPlayed = true;
182192
}
183193
}
184194

185195
var anyTaskCompleted = completedTasks.Count > 0;
186196
if (anyTaskCompleted)
187197
{
198+
context.IsReplaying = context.History[firstCompletedHistoryEventIndex].IsPlayed;
188199
CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context);
189200
// Return a reference to the first completed task
190201
output(firstCompletedTask);

src/Durable/OrchestrationContext.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ public class OrchestrationContext
2020
public object Input { get; internal set; }
2121

2222
[DataMember]
23-
internal string InstanceId { get; set; }
23+
public string InstanceId { get; set; }
2424

2525
[DataMember]
2626
internal string ParentInstanceId { get; set; }
2727

2828
[DataMember]
29-
internal bool IsReplaying { get; set; }
29+
public bool IsReplaying { get; set; }
3030

3131
[DataMember]
3232
internal HistoryEvent[] History { get; set; }

src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psm1

+10-2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ function Get-DurableStatus {
8080
The input value that will be passed to the orchestration Azure Function.
8181
.PARAMETER DurableClient
8282
The orchestration client object.
83+
.PARAMETER InstanceId
84+
The InstanceId for the new orchestration.
8385
#>
8486
function Start-DurableOrchestration {
8587
[CmdletBinding()]
@@ -98,7 +100,11 @@ function Start-DurableOrchestration {
98100

99101
[Parameter(
100102
ValueFromPipelineByPropertyName=$true)]
101-
[object] $DurableClient
103+
[object] $DurableClient,
104+
105+
[Parameter(
106+
ValueFromPipelineByPropertyName=$true)]
107+
[string] $InstanceId
102108
)
103109

104110
$ErrorActionPreference = 'Stop'
@@ -107,7 +113,9 @@ function Start-DurableOrchestration {
107113
$DurableClient = GetDurableClientFromModulePrivateData
108114
}
109115

110-
$InstanceId = (New-Guid).Guid
116+
if (-not $InstanceId) {
117+
$InstanceId = (New-Guid).Guid
118+
}
111119

112120
$Uri =
113121
if ($DurableClient.rpcBaseUrl) {

test/E2E/Azure.Functions.PowerShellWorker.E2E/Azure.Functions.PowerShellWorker.E2E/DurableEndToEndTests.cs

+115
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Azure.Functions.PowerShell.Tests.E2E
1212

1313
using System.Net.Http;
1414
using Newtonsoft.Json;
15+
using System.Text;
1516

1617
[Collection(Constants.FunctionAppCollectionName)]
1718
public class DurableEndToEndTests
@@ -147,6 +148,120 @@ public async Task LegacyDurableCommandNamesStillWork()
147148
}
148149
}
149150

151+
[Fact]
152+
public async Task OrchestratationContextHasAllExpectedProperties()
153+
{
154+
var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClientOrchContextProperties", queryString: string.Empty);
155+
Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode);
156+
157+
var initialResponseBody = await initialResponse.Content.ReadAsStringAsync();
158+
dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody);
159+
var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri;
160+
161+
var startTime = DateTime.UtcNow;
162+
163+
using (var httpClient = new HttpClient())
164+
{
165+
while (true)
166+
{
167+
var statusResponse = await httpClient.GetAsync(statusQueryGetUri);
168+
switch (statusResponse.StatusCode)
169+
{
170+
case HttpStatusCode.Accepted:
171+
{
172+
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
173+
var runtimeStatus = (string)statusResponseBody.runtimeStatus;
174+
Assert.True(
175+
runtimeStatus == "Running" || runtimeStatus == "Pending",
176+
$"Unexpected runtime status: {runtimeStatus}");
177+
178+
if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout)
179+
{
180+
Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}");
181+
}
182+
183+
await Task.Delay(TimeSpan.FromSeconds(2));
184+
break;
185+
}
186+
187+
case HttpStatusCode.OK:
188+
{
189+
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
190+
Assert.Equal("Completed", (string)statusResponseBody.runtimeStatus);
191+
Assert.Equal("True", statusResponseBody.output[0].ToString());
192+
Assert.Equal("Hello myInstanceId", statusResponseBody.output[1].ToString());
193+
Assert.Equal("False", statusResponseBody.output[2].ToString());
194+
return;
195+
}
196+
197+
default:
198+
Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}");
199+
break;
200+
}
201+
}
202+
}
203+
}
204+
205+
[Fact]
206+
public async Task ExternalEventReturnsData()
207+
{
208+
var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClient", queryString: "?FunctionName=DurableOrchestratorRaiseEvent");
209+
Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode);
210+
211+
var initialResponseBody = await initialResponse.Content.ReadAsStringAsync();
212+
dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody);
213+
var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri;
214+
var raiseEventUri = (string)initialResponseBodyObject.sendEventPostUri;
215+
216+
raiseEventUri = raiseEventUri.Replace("{eventName}", "TESTEVENTNAME");
217+
218+
var startTime = DateTime.UtcNow;
219+
220+
using (var httpClient = new HttpClient())
221+
{
222+
while (true)
223+
{
224+
// Send external event payload
225+
var json = JsonConvert.SerializeObject("helloWorld!");
226+
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
227+
await httpClient.PostAsync(raiseEventUri, httpContent);
228+
229+
var statusResponse = await httpClient.GetAsync(statusQueryGetUri);
230+
switch (statusResponse.StatusCode)
231+
{
232+
case HttpStatusCode.Accepted:
233+
{
234+
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
235+
var runtimeStatus = (string)statusResponseBody.runtimeStatus;
236+
Assert.True(
237+
runtimeStatus == "Running" || runtimeStatus == "Pending",
238+
$"Unexpected runtime status: {runtimeStatus}");
239+
240+
if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout)
241+
{
242+
Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}");
243+
}
244+
245+
await Task.Delay(TimeSpan.FromSeconds(2));
246+
break;
247+
}
248+
249+
case HttpStatusCode.OK:
250+
{
251+
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
252+
Assert.Equal("Completed", (string)statusResponseBody.runtimeStatus);
253+
Assert.Equal("helloWorld!", statusResponseBody.output.ToString());
254+
return;
255+
}
256+
257+
default:
258+
Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}");
259+
break;
260+
}
261+
}
262+
}
263+
}
264+
150265
[Fact]
151266
public async Task ActivityCanHaveQueueBinding()
152267
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"bindings": [
3+
{
4+
"authLevel": "function",
5+
"name": "Request",
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"methods": [
9+
"post",
10+
"get"
11+
]
12+
},
13+
{
14+
"type": "http",
15+
"direction": "out",
16+
"name": "Response"
17+
},
18+
{
19+
"name": "starter",
20+
"type": "durableClient",
21+
"direction": "in"
22+
}
23+
]
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using namespace System.Net
2+
3+
param($Request, $TriggerMetadata)
4+
5+
$InstanceId = Start-DurableOrchestration -FunctionName "DurableOrchestratorAccessContextProps" -InstanceId "myInstanceId"
6+
Write-Host "Started orchestration with ID = '$InstanceId'"
7+
8+
$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
9+
Push-OutputBinding -Name Response -Value $Response
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"bindings": [
3+
{
4+
"name": "Context",
5+
"type": "orchestrationTrigger",
6+
"direction": "in"
7+
}
8+
]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
param($Context)
2+
3+
$output = @()
4+
5+
$output += $Context.IsReplaying
6+
$output += Invoke-DurableActivity -FunctionName 'DurableActivity' -Input $Context.InstanceId
7+
$output += $Context.IsReplaying
8+
$output
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"bindings": [
3+
{
4+
"name": "Context",
5+
"type": "orchestrationTrigger",
6+
"direction": "in"
7+
}
8+
]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
param($Context)
2+
3+
$output = @()
4+
5+
$output += Start-DurableExternalEventListener -EventName "TESTEVENTNAME"
6+
7+
$output

0 commit comments

Comments
 (0)