Skip to content

Commit 2f47e2e

Browse files
authored
Add Get-DurableStatus and Stop-DurableOrchestration commands (#629)
1 parent e2e73d1 commit 2f47e2e

File tree

8 files changed

+234
-2
lines changed

8 files changed

+234
-2
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"bindings": [
3+
{
4+
"authLevel": "function",
5+
"type": "httpTrigger",
6+
"direction": "in",
7+
"name": "Request",
8+
"methods": [
9+
"get",
10+
"post"
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+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using namespace System.Net
2+
3+
param($Request, $TriggerMetadata)
4+
5+
Write-Host 'InstanceManagement started'
6+
7+
$InstanceId = Start-DurableOrchestration -FunctionName 'FunctionChainingOrchestrator' -InputObject $Request.Query.Input
8+
Write-Host "Started orchestration $($Request.Params.FunctionName) with ID = '$InstanceId'"
9+
10+
do {
11+
$Status = Get-DurableStatus -InstanceId $InstanceId -ShowHistory -ShowHistoryOutput -ShowInput
12+
Write-Host "Status: $($Status | ConvertTo-Json)"
13+
Start-Sleep -Seconds 5
14+
} while ($Status.runtimeStatus -ne 'Running')
15+
16+
Write-Host "Terminating orchestration $InstanceId..."
17+
Stop-DurableOrchestration -InstanceId $InstanceId -Reason 'Terminated intentionally'
18+
19+
do {
20+
$Status = Get-DurableStatus -InstanceId $InstanceId -ShowHistory -ShowHistoryOutput -ShowInput
21+
Write-Host "Status: $($Status | ConvertTo-Json)"
22+
Start-Sleep -Seconds 5
23+
} while ($Status.runtimeStatus -ne 'Terminated')
24+
25+
$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
26+
Push-OutputBinding -Name Response -Value $Response
27+
28+
Write-Host 'InstanceManagement completed'

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ NestedModules = @('Microsoft.Azure.Functions.PowerShellWorker.psm1', 'Microsoft.
4949

5050
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
5151
FunctionsToExport = @(
52+
'Get-DurableStatus',
5253
'New-DurableRetryOptions',
5354
'New-DurableOrchestrationCheckStatusResponse',
5455
'Send-DurableExternalEvent',
55-
'Start-DurableOrchestration')
56+
'Start-DurableOrchestration',
57+
'Stop-DurableOrchestration')
5658

5759
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
5860
CmdletsToExport = @(

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,53 @@ function GetDurableClientFromModulePrivateData {
1919
}
2020
}
2121

22+
function Get-DurableStatus {
23+
[CmdletBinding()]
24+
param(
25+
[Parameter(
26+
Mandatory = $true,
27+
Position = 0,
28+
ValueFromPipelineByPropertyName = $true)]
29+
[ValidateNotNullOrEmpty()]
30+
[string] $InstanceId,
31+
32+
[Parameter(
33+
ValueFromPipelineByPropertyName = $true)]
34+
[object] $DurableClient,
35+
36+
[switch] $ShowHistory,
37+
38+
[switch] $ShowHistoryOutput,
39+
40+
[switch] $ShowInput
41+
)
42+
43+
$ErrorActionPreference = 'Stop'
44+
45+
if ($null -eq $DurableClient) {
46+
$DurableClient = GetDurableClientFromModulePrivateData
47+
}
48+
49+
$requestUrl = "$($DurableClient.BaseUrl)/instances/$InstanceId"
50+
51+
$query = @()
52+
if ($ShowHistory.IsPresent) {
53+
$query += "showHistory=true"
54+
}
55+
if ($ShowHistoryOutput.IsPresent) {
56+
$query += "showHistoryOutput=true"
57+
}
58+
if ($ShowInput.IsPresent) {
59+
$query += "showInput=true"
60+
}
61+
62+
if ($query.Count -gt 0) {
63+
$requestUrl += "?" + [string]::Join("&", $query)
64+
}
65+
66+
Invoke-RestMethod -Uri $requestUrl
67+
}
68+
2269
<#
2370
.SYNOPSIS
2471
Start an orchestration Azure Function.
@@ -79,6 +126,35 @@ function Start-DurableOrchestration {
79126
return $instanceId
80127
}
81128

129+
function Stop-DurableOrchestration {
130+
[CmdletBinding()]
131+
param(
132+
[Parameter(
133+
Mandatory = $true,
134+
Position = 0,
135+
ValueFromPipelineByPropertyName = $true)]
136+
[ValidateNotNullOrEmpty()]
137+
[string] $InstanceId,
138+
139+
[Parameter(
140+
Mandatory = $true,
141+
Position = 1,
142+
ValueFromPipelineByPropertyName = $true)]
143+
[ValidateNotNullOrEmpty()]
144+
[string] $Reason
145+
)
146+
147+
$ErrorActionPreference = 'Stop'
148+
149+
if ($null -eq $DurableClient) {
150+
$DurableClient = GetDurableClientFromModulePrivateData
151+
}
152+
153+
$requestUrl = "$($DurableClient.BaseUrl)/instances/$InstanceId/terminate?reason=$([System.Web.HttpUtility]::UrlEncode($Reason))"
154+
155+
Invoke-RestMethod -Uri $requestUrl
156+
}
157+
82158
function IsValidUrl([uri]$Url) {
83159
$Url.IsAbsoluteUri -and ($Url.Scheme -in 'http', 'https')
84160
}

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

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,60 @@ private async Task ExternalEventClientSendsExternalEvents() {
414414
}
415415
}
416416
}
417-
417+
418+
[Fact]
419+
public async Task DurableClientTerminatesOrchestration()
420+
{
421+
var initialResponse = await Utilities.GetHttpTriggerResponse("DurableClientTerminating", queryString: string.Empty);
422+
Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode);
423+
424+
var location = initialResponse.Headers.Location;
425+
426+
var initialResponseBody = await initialResponse.Content.ReadAsStringAsync();
427+
dynamic initialResponseBodyObject = JsonConvert.DeserializeObject(initialResponseBody);
428+
var statusQueryGetUri = (string)initialResponseBodyObject.statusQueryGetUri;
429+
430+
var startTime = DateTime.UtcNow;
431+
432+
using (var httpClient = new HttpClient())
433+
{
434+
while (true)
435+
{
436+
var statusResponse = await httpClient.GetAsync(statusQueryGetUri);
437+
switch (statusResponse.StatusCode)
438+
{
439+
case HttpStatusCode.Accepted:
440+
{
441+
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
442+
var runtimeStatus = (string)statusResponseBody.runtimeStatus;
443+
Assert.True(
444+
runtimeStatus == "Running" || runtimeStatus == "Pending",
445+
$"Unexpected runtime status: {runtimeStatus}");
446+
447+
if (DateTime.UtcNow > startTime + _orchestrationCompletionTimeout)
448+
{
449+
Assert.True(false, $"The orchestration has not completed after {_orchestrationCompletionTimeout}");
450+
}
451+
452+
await Task.Delay(TimeSpan.FromSeconds(2));
453+
break;
454+
}
455+
456+
case HttpStatusCode.OK:
457+
{
458+
var statusResponseBody = await GetResponseBodyAsync(statusResponse);
459+
Assert.Equal("Terminated", (string)statusResponseBody.runtimeStatus);
460+
Assert.Equal("Terminated intentionally", (string)statusResponseBody.output);
461+
return;
462+
}
463+
464+
default:
465+
Assert.True(false, $"Unexpected orchestration status code: {statusResponse.StatusCode}");
466+
break;
467+
}
468+
}
469+
}
470+
}
418471

419472
private void VerifyArrayItemsAreEqual(string[] array, int[] indices)
420473
{

test/E2E/TestFunctionApp/DurableClient/run.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ Write-Host "Started orchestration with ID = '$InstanceId'"
1414
$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
1515
Push-OutputBinding -Name Response -Value $Response
1616

17+
$Status = Get-DurableStatus -InstanceId $InstanceId
18+
Write-Host "Orchestration $InstanceId status: $($Status | ConvertTo-Json)"
19+
if ($Status.runtimeStatus -notin 'Pending', 'Running', 'Failed') {
20+
throw "Unexpected orchestration $InstanceId runtime status: $($Status.runtimeStatus)"
21+
}
22+
1723
Write-Host "DurableClient completed"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"bindings": [
3+
{
4+
"authLevel": "anonymous",
5+
"type": "httpTrigger",
6+
"direction": "in",
7+
"name": "Request",
8+
"methods": [
9+
"get",
10+
"post"
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+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using namespace System.Net
2+
3+
param($Request, $TriggerMetadata)
4+
5+
Write-Host "DurableClient started"
6+
7+
$ErrorActionPreference = 'Stop'
8+
9+
$FunctionName = $Request.Query.FunctionName ?? 'DurableOrchestrator'
10+
11+
$InstanceId = Start-DurableOrchestration -FunctionName $FunctionName -InputObject 'Hello'
12+
Write-Host "Started orchestration with ID = '$InstanceId'"
13+
14+
Stop-DurableOrchestration -InstanceId $InstanceId -Reason 'Terminated intentionally'
15+
16+
$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
17+
Push-OutputBinding -Name Response -Value $Response
18+
19+
Write-Host "DurableClient completed"

0 commit comments

Comments
 (0)