Giter VIP home page Giter VIP logo

azure-functions-durable-powershell's Introduction

Durable Functions for PowerShell

This repo hosts the standalone Durable Functions SDK for PowerShell. Compared to the Durable Functions SDK that comes built-in with the Azure Functions PowerShell worker, this standalone SDK contains performance enhancements, more features, and key bug fixes that would have required a breaking release. For more information on this release, please see this article.

The standalone Durable Functions SDK implementation is currently in preview. For production workloads, we recommend continuing to use the built-in Durable Functions SDK which is hosted in this repo.

⚑ Find us in the PowerShell Gallery ⚑.

About Durable Functions

Durable Functions is an extension of Azure Functions that lets you write stateful functions in a serverless compute environment. The extension lets you define stateful workflows by writing orchestrator functions and stateful entities by writing entity functions using the Azure Functions programming model. Behind the scenes, the extension manages state, checkpoints, and restarts for you, allowing you to focus on your business logic.

You can find more information at the following links:

Durable Functions expects certain programming constraints to be followed. Please read the documentation linked above for more information.

Getting Started

Follow these instructions to get started with Durable Functions in PowerShell:

πŸš€ PowerShell Durable Functions quickstart

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

azure-functions-durable-powershell's People

Contributors

michaelpeng36 avatar davidmrdavid avatar microsoftopensource avatar nytian avatar

Stargazers

 avatar おじき | ιš…γ£γ“γ‚¨γƒ³γ‚Έγƒ‹γ‚’ | Hideaki Takahashi avatar Phill Holbrook avatar Eric Schnabel avatar Emanuel Palm avatar

Watchers

Mathew Charles avatar Suwat Ch avatar Finbar Ryan avatar Pragna Gopa avatar Alexey Rodionov avatar Daria Grigoriu avatar Aniello Scotto Di Marco avatar Elliott Hamai avatar Galin Iliev avatar Satish Ranjan avatar  avatar .NET Foundation Contribution License Agreements avatar Sonia Kulkarni avatar Krrish Mittal avatar Varshitha Bachu avatar Surgupta avatar  avatar Omkar More avatar Bala G avatar  avatar Phill Holbrook avatar

azure-functions-durable-powershell's Issues

Durable activity failures still not working properly in Fan out/Fan in orchestrator

Related item https://github.com/Azure/azure-functions-powershell-worker/issues/748

tl;dr: When using Fan out/fan in pattern, and one or more activity fails, the final output in the orchestrator function is dropped.

Still not able to handle activity errors in an orchestrator function when using the fan/out fan/in pattern. Although it seems to report the failure there is no way to get a working output for combinations of successful and failed activities at all. It either discards the successful output, or ignores the failure. Using same code to above issue but using the new SDK.

Trigger (Service Bus)

param([string] $SbMsg, $TriggerMetadata)
$ErrorActionPreference = "Stop"

<#
Service bus message received:
{
  "TestFilter": {
    "version": "1.0.0",
    "environment": "testEnv",
    "itemName": [
      "test1",
      "test2",
      "test3",
      "test4",
      "test5"
    ],
    "appComponent": "testcomponent",
    "application": "testApp"
  }
}
#>

# Declare 5 cities to be randomly picked later
$city = @("London", "Paris", "New York", "Tokyo", "Sydney")

# Create a hashtable the orchestrator could use to iterate over
foreach ($item in $TriggerMetadata.TestFilter.itemName) {
    $hashtableout += @{
        $item = @{
            city     = $city | Get-Random
            itemName = $item
        }
    }
}

# Start the orchestration
$InstanceId = Start-DurableOrchestration -FunctionName "of-test" -Input $hashtableout -ErrorAction Stop
Write-Host "Started orchestration with ID = '$InstanceId'"

Activity Function

param($name)
$ErrorActionPreference = 'Stop'

$actStopwatch = [System.Diagnostics.Stopwatch]::StartNew()

if ($($name.itemName) -eq "test3") { throw }
elseif ($($name.itemName) -eq "test1") { Start-Sleep -Seconds  3}
elseif ($($name.itemName) -eq "test5") { Start-Sleep -Seconds 8 }
elseif ($($name.itemName) -eq "test4") { Start-Sleep -Seconds 50 }

# Finalise outputs
$actStopwatch.stop()
[decimal]$elapsedSecods = $actStopwatch.Elapsed.TotalMinutes
$actTotalTime = "{0:N2}" -f $elapsedSecods
Write-Information -MessageData "[$($name.itemName)] Completed in [$actTotalTime]..."
$name.add('runResults', "Deployment Succeeded. Runtime [$actTotalTime]")
"[$($name.itemName)] Completed in [$actTotalTime]"

So when using the below Orchestrator code everything works as expected regarding the error handling and getting final output from the orchestrator function. The orchestrator schedules the tasks and the final output includes any successful activity outputs as well as failures caught and handled within the orchestrator function. The problem with this orchestrator pattern however is the tasks are asynchronous and therefore do not run efficiently in that they queue behind each other (using sleep statements in the activity highlights this as can be seen below in the scheduled times)

Orchestrator (Working error handling, but pattern not optimal)

using namespace System.Net

param($Context)

$ErrorActionPreference = 'Stop'

$output = @()

foreach ($property in $Context.Input.psobject.properties.name) {
    # invoke the activity function
    try {
        $output += Invoke-DurableActivity -FunctionName "af-test" -Input $Context.Input.$property
    }
    catch {
        $output += "[$($property)] Failed"
    }
}

return $output

image

So the final output highlights the failure meaning the catch block works as expected BUT the scheduling of activities is done in series (function chaining) and therefore causes a completion time build up (last activity might run quickest but has to wait for all others to complete first)

Changing to the Fan out/fan in pattern, the behaviour changes:

using namespace System.Net

param($Context)

$ErrorActionPreference = 'Stop'

$output = @()

$ParallelTasks =
foreach ($property in $Context.Input.psobject.properties.name) {
    # invoke the activity function
    try {
        Invoke-DurableActivity -FunctionName "af-test" -Input $Context.Input.$property -NoWait
    }
    catch {
        $output += "[$($property)] Failed"
    }
}

$output += Wait-DurableTask -Task $ParallelTasks

return $output

And produces this error:
image

Changing to $ErrorActionPreference = 'Continue' allows the orchestrator to complete, but there is no output returned:

image

Moving the try/catch works partially in that it seems to capture the failures, but disregards the successful runs from the output:

using namespace System.Net

param($Context)

$ErrorActionPreference = 'Stop'

$output = @()

$ParallelTasks =
foreach ($property in $Context.Input.psobject.properties.name) {
    # invoke the activity function
    Invoke-DurableActivity -FunctionName "af-test" -Input $Context.Input.$property -NoWait
}

try {
    $output += Wait-DurableTask -Task $ParallelTasks
}
catch {
    $output += "[$($property)] Failed"
}

return $output

image

It also does not collect multiple failures:
image

Using the same logic as in the fan out/fan in section here: https://github.com/Azure/azure-functions-durable-powershell/blob/main/test/E2E/durableApp/DurablePatternsOrchestrator/run.ps1 .

using namespace System.Net

param($Context)

$ErrorActionPreference = 'Continue'

$output = @()

$ParallelTasks = @()

foreach ($property in $Context.Input.psobject.properties.name) {
    # invoke the activity function
    try {
        $ParallelTasks += Invoke-DurableActivity -FunctionName "af-test" -Input $Context.Input.$property -NoWait
    }
    catch {
        $ParallelTasks += "[$($property)] Failed"
    }
}

$output += Wait-DurableTask -Task $ParallelTasks

return $output

Again runs successfully, but does not handle the error or produce any final output. However, using this same orchestrator code but with no failing activities works and produces the final output:

image

So, to me it seems that whenever one or more activities fail, it just nukes any final output or captures only one.

I've tried relentlessly but cannot get the Fan out/fan in orchestrator to properly output when activities fail. I've also tried setting various different options for the $ErrorActionPreference to no avail. Would be great if error handling could work similar to when the chaining pattern is used and final output is generated with success or failures or both. Or perhaps there is a way to individually query the task results when using the Fan out/fan in pattern to achieve a similar result?

Update templates for external SDK

The external SDK needs to be imported manually by the user in their profile.ps1. We will need to update our templates to demonstrate how to do this.

We will need to modify the templates for VSCode, and core-tools. There may be more template sources in other places as well that will need updating.

Action required: migrate or opt-out of migration to GitHub inside Microsoft

Migrate non-Open Source or non-External Collaboration repositories to GitHub inside Microsoft

In order to protect and secure Microsoft, private or internal repositories in GitHub for Open Source which are not related to open source projects or require collaboration with 3rd parties (customer, partners, etc.) must be migrated to GitHub inside Microsoft a.k.a GitHub Enterprise Cloud with Enterprise Managed User (GHEC EMU).

Action

✍️ Please RSVP to opt-in or opt-out of the migration to GitHub inside Microsoft.

❗Only users with admin permission in the repository are allowed to respond. Failure to provide a response will result to your repository getting automatically archived.πŸ”’

Instructions

Reply with a comment on this issue containing one of the following optin or optout command options below.

βœ… Opt-in to migrate

@gimsvc optin --date <target_migration_date in mm-dd-yyyy format>

Example: @gimsvc optin --date 03-15-2023

OR

❌ Opt-out of migration

@gimsvc optout --reason <staging|collaboration|delete|other>

Example: @gimsvc optout --reason staging

Options:

  • staging : This repository will ship as Open Source or go public
  • collaboration : Used for external or 3rd party collaboration with customers, partners, suppliers, etc.
  • delete : This repository will be deleted because it is no longer needed.
  • other : Other reasons not specified

Need more help? πŸ–οΈ

Refine the `DurableTask` written to output by `WaitAny`

WaitAny writes the first DurableTask to complete to the output pipe. However, the information displayed is not useful, showing type information. It would be good to make sure the output at least matches what is present in the PowerShell worker or displays more useful information (for example, the external event name in a ExternalEvent task).

Get-DurableStatus still causing Error 401 (Unauthorized) in Azure Function App

This issue is still happening in Azure Durable Functions via HTTP starter function.

Apparently this has been fixed, but it does not actually seem to be, see:

To rule out any issues in my own code & functions, I also tested using Microsoft's own examples, showing the issue persists:

Using template:

  • DurableClient as regAPStart
  • DurableOrchestrator as regAPOrch
  • DuragleActivity as regAPAct

The orchestrator and activity are running just fine.

The Starter works fine until it gets to the following line:
$Status = Get-DurableStatus -InstanceId $InstanceId

Starter returns:

2024-05-03T18:35:58Z   [Information]   INFORMATION: DurableClient started
2024-05-03T18:35:58Z   [Information]   INFORMATION: Started orchestration with ID = 'b937fc9d-a34d-4407-a876-c9cdd2f18c2e', FunctionName = 'regAPOrch'
2024-05-03T18:35:58Z   [Error]   EXCEPTION: Response status code does not indicate success: 401 (Unauthorized).

Orchestrator returns:

2024-05-03T18:39:51Z   [Information]   INFORMATION: DurableOrchestrator: started. Input: Hello
2024-05-03T18:39:51Z   [Information]   INFORMATION: DurableOrchestrator: finished.
2024-05-03T18:39:51Z   [Information]   OUTPUT: Hello Tokyo
2024-05-03T18:39:51Z   [Information]   <id>: Function 'regAPOrch (Orchestrator)' completed.

Activity returns:

2024-05-03T18:39:41Z   [Information]   INFORMATION: DurableActivity(Tokyo) started
2024-05-03T18:39:51Z   [Information]   INFORMATION: DurableActivity(Tokyo) finished
2024-05-03T18:39:51Z   [Information]   Executed 'Functions.regAPAct'

Still can't seem to pass complex output to activity functions

Related Issue: Azure/azure-functions-durable-extension#1922

If I build up a ps object with sub-objects in an orchestration function and send that to an activity function, it ends up as a hashtable on the other side:

$InputData = [PSCustomObject]@{
            Data = $Data
            Documents = $Documents
            SyncDataType = $SyncDataType
        }
Invoke-DurableActivity -FunctionName 'Sync-DataWithDocuments' -Input $InputData

Maybe this is just a limitation but I want to be sure before I use workarounds. I can get it to work if I do the following:

$InputData = [PSCustomObject]@{
            Data = $Data
            Documents = $Documents
            SyncDataType = $SyncDataType
        }
Invoke-DurableActivity -FunctionName 'Sync-DataWithDocuments' -Input ($InputData | ConvertTo-Json -Depth 100)

Then in the activity function

function Sync-DataWithDocuments {
    Param($Object)
    $Object = $Object | ConvertTo-Json -Depth 100 | ConvertFrom-Json
}

Action required: self-attest your goal for this repository

It's time to review and renew the intent of this repository

An owner or administrator of this repository has previously indicated that this repository can not be migrate to GitHub inside Microsoft because it is going public, open source, or it is used to collaborate with external parties (customers, partners, suppliers, etc.).

Action

πŸ‘€ ✍️ In order to keep Microsoft secure, we require repository owners and administrators to review this repository and regularly renew the intent whether to opt-in or opt-out of migration to GitHub inside Microsoft which is specifically intended for private or internal projects.

❗Only users with admin permission in the repository are allowed to respond. Failure to provide a response will result to your repository getting automatically archived. πŸ”’

Instructions

❌ Opt-out of migration

If this repository can not be migrated to GitHub inside Microsoft, you can opt-out of migration by replying with a comment on this issue containing one of the following optout command options below.

@gimsvc optout --reason <staging|collaboration|delete|other>

Example: @gimsvc optout --reason staging

Options:

  • staging : My project will ship as Open Source
  • collaboration : Used for external or 3rd party collaboration with customers, partners, suppliers, etc.
  • delete : This repository will be deleted because it is no longer needed.
  • other : Other reasons not specified

βœ… Opt-in to migrate

If the circumstances of this repository has changed and you decide that you need to migrate, then you can specify the optin command below. For example, the repository is no longer going public, open source or require external collaboration.

@gimsvc optin --date <target_migration_date in mm-dd-yyyy format>

Example: @gimsvc optin --date 03-15-2023

Click here for more information about optin and optout command options and examples

Opt-in

@gimsvc optin --date <target_migration_date>

When opting-in to migrate your repository, the --date option is required followed by your specified migration date using the format: mm-dd-yyyy

@gimsvc optin --date 03-15-2023

Opt-out

@gimsvc optout --reason <staging|collaboration|delete|other>

When opting-out of migration, you need to specify the --reason.

  • staging
    • My project will ship as Open Source
  • collaboration
    • Used for external or 3rd party collaboration with customers, partners, suppliers, etc.
  • delete
    • This repository will be deleted because it is no longer needed.
  • other
    • Other reasons not specified

Examples:

@gimsvc optout --reason staging

@gimsvc optout --reason collaboration

@gimsvc optout --reason delete

@gimsvc optout --reason other

Need more help? πŸ–οΈ

Remove `External` and `E` suffixes from Durable cmdlets

Currently, cmdlets in the external durable SDK have External and E suffixes and corresponding print statements to distinguish them from cmdlets present in the PowerShell worker. These should be removed before the SDK is fully released.

Invoke-WebRequest hangs inside of Activity Function

I have a function that uses Invoke-WebRequest in order to pull data from an external API. Here's a sanitzed version of the function:

function Get-APIResource {
    param(
        [Parameter (Mandatory=$true)]$Resource,
        [Parameter (Mandatory=$true)]$SearchQuery
    )

    $BaseURL = ""

    $headers = @{
        ApiCode = $env:APISecret
        UserName = $env:PIUser
        Secret = $env:APIKey
        'Content-Type' = 'application/json'
    }

    $ResourceURL = $BaseURL + $Resource + '/query?search=' + $SearchQuery

    $i = 1

    try {

        do {

            Write-Warning "Getting page $i of $Resource"
            $Response = Invoke-WebRequest -Method GET -Uri $ResourceURL -Headers $headers -TimeoutSec 30 -ErrorAction Stop
            $Response = $Response.Content | ConvertFrom-Json
            
            If ($Response.items){

                Write-Warning "($i) Retrieved $($Response.items.count) items"

                $Response.items | ForEach-Object{
                    $_
                    #$ReturnList.Add($_)
                }
            }
    
            If ($Response.item){

                Write-Warning "($i) Retrieved $($Response.item.count) ITEM"

                $Response.item | ForEach-Object{
                    $_
                    #$ReturnList.Add($_)
                }
            }
    
            $ResourceURL = $Response.pagedetails.nextPageUrl

            If ($null -eq $ResourceURL){
                Write-Warning "($i) ResourceURL is null"
            } Else {
                Write-Warning "($i) Next page URL: $ResourceURL"
            }

            $i++

        } while ($null -ne $ResourceURL)

    }
    catch {
        Write-Error "Connecting to API Failed."
        throw "API Error: $($_.Exception.Message)"    
    }

}

I'm using it in an Activity function, like so:

param($Context)

# Set DataType
$DataType = 'Products'

# Initialize default return object
$returnObject = [PSCustomObject]@{
    $DataType = "Problem accessing data."
}

# Get data
try{
    $filter = @{} | ConvertTo-JSON
    $Data = Get-APIResource -Resource Products -SearchQuery $filter
}
catch{
    Write-Warning "Error retrieving $DataType."
    Write-Error "ERROR: $($Error[0].Exception.Message)"
    If ($Error[0].InnerException){
        Write-Error "INNER ERROR: $($Error[0].Exception.InnerException)"
    }
    throw $returnObject
}

If (-not $Data){
    Write-Warning "`$Data variable is blank. Failed to get $DataType."
    return $returnObject
}

# Return
[PSCustomObject]@{
    $DataType = $Data
} | ConvertTo-Json -Depth 100

And this is being called by an Orchestrator function:

param($Context)

Write-Output "Starting Orchestrator."

$RetryOptions = New-DurableRetryOptions -FirstRetryInterval (New-TimeSpan -Seconds 5) -MaxNumberOfAttempts 3

Write-Output "Retrieving Config Items from Autotask"
$APIData = Invoke-DurableActivity -FunctionName "Func-RetrieveAPIData" -RetryOptions $RetryOptions

# Build object (add metadata) to send to data lake
$SendObject = @{
    APIData = $APIData
    sourceName = "API"
}

# Convert to JSON to prep for sending
$SendJson = $SendObject | ConvertTo-Json -Depth 100

# Send to the data lake function
Write-Output "Sending Data to Data Lake function"
Invoke-DurableActivity -FunctionName "Func-SendtoDataLake" -Input $SendJson

When I look at Log Stream in the Azure Function, I can see the progress of Invoke-WebRequest. When it runs normally, it looks like this:

PROGRESS: Reading response stream... (Number of bytes read: 0)
PROGRESS: Reading response stream... (Number of bytes read: 3276)
PROGRESS: Reading response stream... (Number of bytes read: 13276)
PROGRESS: Reading response stream... (Number of bytes read: 23276)
PROGRESS: Reading response stream... (Number of bytes read: 27864)
...
...
PROGRESS: Reading response stream... (Number of bytes read: 256662)
PROGRESS: Reading web response completed. (Number of bytes read: 256662)

However, when it has a problem, it will halt after a random number of bytes read. Verbose logs will continue to output from normal operation of the Azure Function. The Activity Function times out after 10 minutes, at which point the Orchestrator finishes.

Normally, the API request takes under 30 seconds to complete -- usually it completes in under 10 seconds. But since the web stream from Invoke-WebRequest is hanging the Activity Function up, I cannot implement Durable Retry or Durable Wait in the Orchestrator function. I've tried wrapping each Invoke-WebRequest in Start-ThreadJob within Get-APIResource and using Wait-Job with a 30 second timeout. I'm able to properly throw an error from the API function to the Activity function, and the Orchestrator function catches it, but the Activity Function still sits there and waits until 10 minutes to time out because the thread job is locked up thanks to the Invoke-WebRequest stream.

I cannot reproduce this problem locally (via manual execution of Get-APIResource or via Azure Function debugging in VS Code) or on an Azure VM. I can fairly reliably reproduce this problem within Azure Automation Accounts / Runbooks and Azure Functions.

I've been chasing this error down since at least September 2023. I'm happy to share any other required information to help track this down.

Linux ADO agents may fail test runs due to timeouts

Currently, pipeline runs on Linux may fail due to either orchestrator timeouts or due to HttpClient timeouts. These may be due to worker concurrency settings not being optimized for test runs, but investigation is required to make runs more reliable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.