Skip to content

Commit 2e15342

Browse files
authored
Add suspend-resume support for durable client (#72)
As titled.
1 parent 959cf1a commit 2e15342

File tree

5 files changed

+140
-2
lines changed

5 files changed

+140
-2
lines changed

src/AzureFunctions.PowerShell.Durable.SDK.psd1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
'Get-DurableStatus',
3838
'New-DurableOrchestrationCheckStatusResponse',
3939
'Send-DurableExternalEvent',
40-
'Start-DurableOrchestration'
41-
'Stop-DurableOrchestration'
40+
'Start-DurableOrchestration',
41+
'Stop-DurableOrchestration',
42+
'Suspend-DurableOrchestration',
43+
'Resume-DurableOrchestration'
4244
)
4345

4446
# 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.

src/AzureFunctions.PowerShell.Durable.SDK.psm1

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,64 @@ function Stop-DurableOrchestration {
164164
Invoke-RestMethod -Uri $requestUrl -Method 'POST'
165165
}
166166

167+
function Suspend-DurableOrchestration {
168+
[CmdletBinding()]
169+
param(
170+
[Parameter(
171+
Mandatory = $true,
172+
Position = 0,
173+
ValueFromPipelineByPropertyName = $true)]
174+
[ValidateNotNullOrEmpty()]
175+
[string] $InstanceId,
176+
177+
[Parameter(
178+
Mandatory = $true,
179+
Position = 1,
180+
ValueFromPipelineByPropertyName = $true)]
181+
[ValidateNotNullOrEmpty()]
182+
[string] $Reason
183+
)
184+
185+
$ErrorActionPreference = 'Stop'
186+
187+
if ($null -eq $DurableClient) {
188+
$DurableClient = GetDurableClientFromModulePrivateData
189+
}
190+
191+
$requestUrl = "$($DurableClient.BaseUrl)/instances/$InstanceId/suspend?reason=$([System.Web.HttpUtility]::UrlEncode($Reason))"
192+
193+
Invoke-RestMethod -Uri $requestUrl -Method 'POST'
194+
}
195+
196+
function Resume-DurableOrchestration {
197+
[CmdletBinding()]
198+
param(
199+
[Parameter(
200+
Mandatory = $true,
201+
Position = 0,
202+
ValueFromPipelineByPropertyName = $true)]
203+
[ValidateNotNullOrEmpty()]
204+
[string] $InstanceId,
205+
206+
[Parameter(
207+
Mandatory = $true,
208+
Position = 1,
209+
ValueFromPipelineByPropertyName = $true)]
210+
[ValidateNotNullOrEmpty()]
211+
[string] $Reason
212+
)
213+
214+
$ErrorActionPreference = 'Stop'
215+
216+
if ($null -eq $DurableClient) {
217+
$DurableClient = GetDurableClientFromModulePrivateData
218+
}
219+
220+
$requestUrl = "$($DurableClient.BaseUrl)/instances/$InstanceId/resume?reason=$([System.Web.HttpUtility]::UrlEncode($Reason))"
221+
222+
Invoke-RestMethod -Uri $requestUrl -Method 'POST'
223+
}
224+
167225
function IsValidUrl([uri]$Url) {
168226
$Url.IsAbsoluteUri -and ($Url.Scheme -in 'http', 'https')
169227
}

test/E2E/AzureFunctions.PowerShell.Durable.SDK.E2E/DurableClientTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,35 @@ await ValidateDurableWorkflowResults(
227227
Assert.Equal("Terminated intentionally", (string)finalStatusResponseBody.output);
228228
});
229229
}
230+
231+
[Fact]
232+
public async Task DurableClientSuspendOrchestration()
233+
{
234+
var initialResponse = await Utilities.GetHttpStartResponse(
235+
orchestratorName: "SendDurableExternalEventOrchestrator",
236+
clientRoute: "suspendingOrchestrators");
237+
Assert.Equal(HttpStatusCode.Accepted, initialResponse.StatusCode);
238+
239+
await ValidateDurableWorkflowResults(
240+
initialResponse,
241+
validateIntermediateResponse: (dynamic intermediateStatusResponseBody) =>
242+
{
243+
Assert.Equal("Suspended", (string)intermediateStatusResponseBody.runtimeStatus);
244+
Assert.Equal("Suspend orchestrator", (string)intermediateStatusResponseBody.output);
245+
});
246+
247+
await ValidateDurableWorkflowResults(
248+
initialResponse,
249+
validateIntermediateResponse: (dynamic intermediateStatusResponseBody) =>
250+
{
251+
Assert.Equal("Running", (string)intermediateStatusResponseBody.runtimeStatus);
252+
},
253+
validateFinalResponse: (dynamic finalStatusResponseBody) =>
254+
{
255+
Assert.Equal("Completed", (string)finalStatusResponseBody.runtimeStatus);
256+
Assert.Equal("FirstTimeout", finalStatusResponseBody.output[0].ToString());
257+
Assert.Equal("SecondExternalEvent", finalStatusResponseBody.output[1].ToString());
258+
});
259+
}
230260
}
231261
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"bindings": [
3+
{
4+
"authLevel": "function",
5+
"name": "Request",
6+
"type": "httpTrigger",
7+
"direction": "in",
8+
"route": "suspendingOrchestrators/{FunctionName}",
9+
"methods": [
10+
"post",
11+
"get"
12+
]
13+
},
14+
{
15+
"type": "http",
16+
"direction": "out",
17+
"name": "Response"
18+
},
19+
{
20+
"name": "starter",
21+
"type": "durableClient",
22+
"direction": "in"
23+
}
24+
]
25+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
param($Request, $TriggerMetadata)
2+
$ErrorActionPreference = 'Stop'
3+
4+
Write-Host "DurableClientSuspending started"
5+
6+
$OrchestratorInputs = @{ FirstDuration = 5; SecondDuration = 60 }
7+
8+
$FunctionName = $Request.Params.FunctionName
9+
$InstanceId = Start-DurableOrchestration -FunctionName $FunctionName -InputObject $OrchestratorInputs
10+
Write-Host "Started orchestration with ID = '$InstanceId'"
11+
12+
Start-Sleep -Seconds 5
13+
Suspend-DurableOrchestration -InstanceId $InstanceId -Reason 'Suspend orchestrator'
14+
15+
$SuspendResponse = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
16+
Push-OutputBinding -Name Response -Value $SuspendResponse
17+
18+
Start-Sleep -Seconds 10
19+
Resume-DurableOrchestration -InstanceId $InstanceId -Reason 'Resume orchestrator'
20+
21+
Send-DurableExternalEvent -InstanceId $InstanceId -EventName "SecondExternalEvent"
22+
23+
Write-Host "DurableClientSuspending completed"

0 commit comments

Comments
 (0)