Skip to content

Commit

Permalink
Port "Fix various DF bugs" to v4.x/ps7.0 (#796)
Browse files Browse the repository at this point in the history
* Fix various DF bugs (#783)

* remove release_notes.md
  • Loading branch information
davidmrdavid authored May 24, 2022
1 parent 2173c49 commit 004591d
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 6 deletions.
1 change: 0 additions & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
* Bug fix: Activity Functions can now use output bindings (https://github.com/Azure/azure-functions-powershell-worker/issues/646)
13 changes: 12 additions & 1 deletion src/Durable/DurableTaskHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public void StopAndInitiateDurableTaskOrReplay(
}

completedHistoryEvent.IsProcessed = true;
context.IsReplaying = completedHistoryEvent.IsPlayed;

switch (completedHistoryEvent.EventType)
{
Expand All @@ -57,7 +58,13 @@ public void StopAndInitiateDurableTaskOrReplay(
output(eventResult);
}
break;

case HistoryEventType.EventRaised:
var eventRaisedResult = GetEventResult(completedHistoryEvent);
if (eventRaisedResult != null)
{
output(eventRaisedResult);
}
break;
case HistoryEventType.TaskFailed:
if (retryOptions == null)
{
Expand Down Expand Up @@ -126,6 +133,7 @@ public void WaitAll(
var allTasksCompleted = completedEvents.Count == tasksToWaitFor.Count;
if (allTasksCompleted)
{
context.IsReplaying = completedEvents.Count == 0 ? false : completedEvents[0].IsPlayed;
CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context);

foreach (var completedHistoryEvent in completedEvents)
Expand Down Expand Up @@ -164,6 +172,7 @@ public void WaitAny(
if (scheduledHistoryEvent != null)
{
scheduledHistoryEvent.IsProcessed = true;
scheduledHistoryEvent.IsPlayed = true;
}

if (completedHistoryEvent != null)
Expand All @@ -179,12 +188,14 @@ public void WaitAny(
}

completedHistoryEvent.IsProcessed = true;
completedHistoryEvent.IsPlayed = true;
}
}

var anyTaskCompleted = completedTasks.Count > 0;
if (anyTaskCompleted)
{
context.IsReplaying = context.History[firstCompletedHistoryEventIndex].IsPlayed;
CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context);
// Return a reference to the first completed task
output(firstCompletedTask);
Expand Down
4 changes: 2 additions & 2 deletions src/Durable/OrchestrationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public class OrchestrationContext
public object Input { get; internal set; }

[DataMember]
internal string InstanceId { get; set; }
public string InstanceId { get; set; }

[DataMember]
internal string ParentInstanceId { get; set; }

[DataMember]
internal bool IsReplaying { get; set; }
public bool IsReplaying { get; set; }

[DataMember]
internal HistoryEvent[] History { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ function Get-DurableStatus {
The input value that will be passed to the orchestration Azure Function.
.PARAMETER DurableClient
The orchestration client object.
.PARAMETER InstanceId
The InstanceId for the new orchestration.
#>
function Start-DurableOrchestration {
[CmdletBinding()]
Expand All @@ -98,7 +100,11 @@ function Start-DurableOrchestration {

[Parameter(
ValueFromPipelineByPropertyName=$true)]
[object] $DurableClient
[object] $DurableClient,

[Parameter(
ValueFromPipelineByPropertyName=$true)]
[string] $InstanceId
)

$ErrorActionPreference = 'Stop'
Expand All @@ -107,7 +113,9 @@ function Start-DurableOrchestration {
$DurableClient = GetDurableClientFromModulePrivateData
}

$InstanceId = (New-Guid).Guid
if (-not $InstanceId) {
$InstanceId = (New-Guid).Guid
}

$Uri =
if ($DurableClient.rpcBaseUrl) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Azure.Functions.PowerShell.Tests.E2E

using System.Net.Http;
using Newtonsoft.Json;
using System.Text;

[Collection(Constants.FunctionAppCollectionName)]
public class DurableEndToEndTests
Expand Down Expand Up @@ -147,6 +148,120 @@ public async Task LegacyDurableCommandNamesStillWork()
}
}

[Fact]
public async Task OrchestratationContextHasAllExpectedProperties()
{
var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClientOrchContextProperties", queryString: string.Empty);
Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode);

var initialResponseBody = await initialResponse.Content.ReadAsStringAsync();
dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody);
var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri;

var startTime = DateTime.UtcNow;

using (var httpClient = new HttpClient())
{
while (true)
{
var statusResponse = await httpClient.GetAsync(statusQueryGetUri);
switch (statusResponse.StatusCode)
{
case HttpStatusCode.Accepted:
{
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
var runtimeStatus = (string)statusResponseBody.runtimeStatus;
Assert.True(
runtimeStatus == "Running" || runtimeStatus == "Pending",
$"Unexpected runtime status: {runtimeStatus}");

if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout)
{
Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}");
}

await Task.Delay(TimeSpan.FromSeconds(2));
break;
}

case HttpStatusCode.OK:
{
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
Assert.Equal("Completed", (string)statusResponseBody.runtimeStatus);
Assert.Equal("True", statusResponseBody.output[0].ToString());
Assert.Equal("Hello myInstanceId", statusResponseBody.output[1].ToString());
Assert.Equal("False", statusResponseBody.output[2].ToString());
return;
}

default:
Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}");
break;
}
}
}
}

[Fact]
public async Task ExternalEventReturnsData()
{
var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClient", queryString: "?FunctionName=DurableOrchestratorRaiseEvent");
Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode);

var initialResponseBody = await initialResponse.Content.ReadAsStringAsync();
dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody);
var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri;
var raiseEventUri = (string)initialResponseBodyObject.sendEventPostUri;

raiseEventUri = raiseEventUri.Replace("{eventName}", "TESTEVENTNAME");

var startTime = DateTime.UtcNow;

using (var httpClient = new HttpClient())
{
while (true)
{
// Send external event payload
var json = JsonConvert.SerializeObject("helloWorld!");
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
await httpClient.PostAsync(raiseEventUri, httpContent);

var statusResponse = await httpClient.GetAsync(statusQueryGetUri);
switch (statusResponse.StatusCode)
{
case HttpStatusCode.Accepted:
{
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
var runtimeStatus = (string)statusResponseBody.runtimeStatus;
Assert.True(
runtimeStatus == "Running" || runtimeStatus == "Pending",
$"Unexpected runtime status: {runtimeStatus}");

if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout)
{
Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}");
}

await Task.Delay(TimeSpan.FromSeconds(2));
break;
}

case HttpStatusCode.OK:
{
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
Assert.Equal("Completed", (string)statusResponseBody.runtimeStatus);
Assert.Equal("helloWorld!", statusResponseBody.output.ToString());
return;
}

default:
Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}");
break;
}
}
}
}

[Fact]
public async Task ActivityCanHaveQueueBinding()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"bindings": [
{
"authLevel": "function",
"name": "Request",
"type": "httpTrigger",
"direction": "in",
"methods": [
"post",
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "Response"
},
{
"name": "starter",
"type": "durableClient",
"direction": "in"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using namespace System.Net

param($Request, $TriggerMetadata)

$InstanceId = Start-DurableOrchestration -FunctionName "DurableOrchestratorAccessContextProps" -InstanceId "myInstanceId"
Write-Host "Started orchestration with ID = '$InstanceId'"

$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
Push-OutputBinding -Name Response -Value $Response
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"bindings": [
{
"name": "Context",
"type": "orchestrationTrigger",
"direction": "in"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
param($Context)

$output = @()

$output += $Context.IsReplaying
$output += Invoke-DurableActivity -FunctionName 'DurableActivity' -Input $Context.InstanceId
$output += $Context.IsReplaying
$output
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"bindings": [
{
"name": "Context",
"type": "orchestrationTrigger",
"direction": "in"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
param($Context)

$output = @()

$output += Start-DurableExternalEventListener -EventName "TESTEVENTNAME"

$output

0 comments on commit 004591d

Please sign in to comment.