Skip to content

Commit 355a713

Browse files
Automated tests for Azure Functions (microsoft#40)
* Adding azure functions samples and test * Updated to use azure-functions/java docker image * Updated Extensions.DurableTask to 2.7.1 and azure-functions-java-library to 2.0.1 * Use latest java worker * Updated to use ExtensionBundle.Preview * Updated samples for jdk 8 * Updated pipeline to use jdk 8 * Fixed minor issues
1 parent 51735e4 commit 355a713

File tree

10 files changed

+272
-2
lines changed

10 files changed

+272
-2
lines changed

.github/workflows/build-validation.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,33 @@ jobs:
5757
with:
5858
name: Package
5959
path: sdk/build/libs
60+
61+
functions:
62+
63+
needs: build
64+
runs-on: ubuntu-latest
65+
66+
steps:
67+
- uses: actions/checkout@v2
68+
with:
69+
submodules: true
70+
- name: Set up JDK 8
71+
uses: actions/setup-java@v2
72+
with:
73+
java-version: '8'
74+
distribution: 'temurin'
75+
- name: Publish to local
76+
uses: gradle/gradle-build-action@bc3340afc5e3cc44f2321809ac090d731c13c514
77+
with:
78+
arguments: publishToMavenLocal
79+
- name: Build azure functions sample
80+
uses: gradle/gradle-build-action@bc3340afc5e3cc44f2321809ac090d731c13c514
81+
with:
82+
arguments: azureFunctionsPackage
83+
continue-on-error: true
84+
- name: Download azure functions java library # TODO: Remove this step once gradle plugin is updated
85+
run: |
86+
wget -P samples-azure-functions/build/azure-functions/azure-functions-sample/lib/ "https://repo.maven.apache.org/maven2/com/microsoft/azure/functions/azure-functions-java-library/2.0.1/azure-functions-java-library-2.0.1.jar" --show-progress
87+
- name: Run azure functions test
88+
run: samples-azure-functions/e2e-test.ps1 -DockerfilePath samples-azure-functions/Dockerfile -HttpStartPath api/StartOrchestration
89+
shell: pwsh

azurefunctions/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ repositories {
1717

1818
dependencies {
1919
api project(':sdk')
20-
implementation group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '2.0.1-20220407.215042-1'
20+
implementation group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '2.0.1'
2121
implementation "com.google.protobuf:protobuf-java:${protocVersion}"
2222
}
2323

samples-azure-functions/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM mcr.microsoft.com/azure-functions/java:4-java11
2+
3+
RUN rm /azure-functions-host/workers/java/azure-functions-java-worker.jar
4+
COPY samples-azure-functions/azure-functions-java-worker-2.2.3.jar /azure-functions-host/workers/java/azure-functions-java-worker.jar
5+
COPY samples-azure-functions/build/azure-functions/azure-functions-sample/ /home/site/wwwroot/
6+
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
7+
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
Binary file not shown.

samples-azure-functions/build.gradle

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
plugins {
2+
id "com.microsoft.azure.azurefunctions" version "1.9.0"
3+
}
4+
apply plugin: 'java'
5+
apply plugin: "com.microsoft.azure.azurefunctions"
6+
7+
group 'com.functions'
8+
version '0.1.0-SNAPSHOT'
9+
10+
repositories {
11+
mavenLocal()
12+
maven {
13+
url "https://oss.sonatype.org/content/repositories/snapshots/"
14+
}
15+
mavenCentral()
16+
}
17+
18+
dependencies {
19+
implementation project(':sdk')
20+
implementation project(':azurefunctions')
21+
22+
implementation 'com.microsoft.azure.functions:azure-functions-java-library:2.0.1'
23+
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
24+
testImplementation 'org.mockito:mockito-core:3.3.3'
25+
runtimeOnly "io.grpc:grpc-netty-shaded:1.38.0"
26+
}
27+
28+
sourceCompatibility = '1.8'
29+
targetCompatibility = '1.8'
30+
31+
compileJava.options.encoding = 'UTF-8'
32+
33+
azurefunctions {
34+
resourceGroup = 'java-functions-group'
35+
appName = 'azure-functions-sample'
36+
pricingTier = 'Consumption'
37+
region = 'westus'
38+
runtime {
39+
os = 'Windows'
40+
javaVersion = 'Java 8'
41+
}
42+
auth {
43+
type = 'azure_cli'
44+
}
45+
localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
46+
}

samples-azure-functions/e2e-test.ps1

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Installing PowerShell: https://docs.microsoft.com/powershell/scripting/install/installing-powershell
2+
3+
param(
4+
[Parameter(Mandatory=$true)]
5+
[string]$DockerfilePath,
6+
[Parameter(Mandatory=$true)]
7+
[string]$HttpStartPath,
8+
[string]$ImageName="dfapp",
9+
[string]$ContainerName="app",
10+
[switch]$NoSetup=$false,
11+
[switch]$NoValidation=$false,
12+
[string]$AzuriteVersion="3.15.0",
13+
[int]$Sleep=30
14+
)
15+
16+
$ErrorActionPreference = "Stop"
17+
18+
if ($NoSetup -eq $false) {
19+
# Build the docker image first, since that's the most critical step
20+
Write-Host "Building sample app Docker container from '$DockerfilePath'..." -ForegroundColor Yellow
21+
docker build -f $DockerfilePath -t $ImageName --progress plain .
22+
23+
# Next, download and start the Azurite emulator Docker image
24+
Write-Host "Pulling down the mcr.microsoft.com/azure-storage/azurite:$AzuriteVersion image..." -ForegroundColor Yellow
25+
docker pull "mcr.microsoft.com/azure-storage/azurite:${AzuriteVersion}"
26+
27+
Write-Host "Starting Azurite storage emulator using default ports..." -ForegroundColor Yellow
28+
docker run --name 'azurite' -p 10000:10000 -p 10001:10001 -p 10002:10002 -d "mcr.microsoft.com/azure-storage/azurite:${AzuriteVersion}"
29+
30+
# Finally, start up the smoke test container, which will connect to the Azurite container
31+
docker run --name $ContainerName -p 8080:80 -it --add-host=host.docker.internal:host-gateway -d `
32+
--env 'AzureWebJobsStorage=UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://host.docker.internal' `
33+
--env 'WEBSITE_HOSTNAME=localhost:8080' `
34+
$ImageName
35+
}
36+
37+
if ($sleep -gt 0) {
38+
# The container needs a bit more time before it can start receiving requests
39+
Write-Host "Sleeping for $Sleep seconds to let the container finish initializing..." -ForegroundColor Yellow
40+
Start-Sleep -Seconds $Sleep
41+
}
42+
43+
# Check to see what containers are running
44+
docker ps
45+
46+
try {
47+
# Make sure the Functions runtime is up and running
48+
$pingUrl = "http://localhost:8080/admin/host/ping"
49+
Write-Host "Pinging app at $pingUrl to ensure the host is healthy" -ForegroundColor Yellow
50+
Invoke-RestMethod -Method Post -Uri "http://localhost:8080/admin/host/ping"
51+
52+
if ($NoValidation -eq $false) {
53+
# Note that any HTTP protocol errors (e.g. HTTP 4xx or 5xx) will cause an immediate failure
54+
$startOrchestrationUri = "http://localhost:8080/$HttpStartPath"
55+
Write-Host "Starting a new orchestration instance via POST to $startOrchestrationUri..." -ForegroundColor Yellow
56+
57+
$result = Invoke-RestMethod -Method Post -Uri $startOrchestrationUri
58+
Write-Host "Started orchestration with instance ID '$($result.id)'!" -ForegroundColor Yellow
59+
Write-Host "Waiting for orchestration to complete..." -ForegroundColor Yellow
60+
61+
$retryCount = 0
62+
$success = $false
63+
$statusUrl = $result.statusQueryGetUri
64+
65+
while ($retryCount -lt 15) {
66+
$result = Invoke-RestMethod -Method Get -Uri $statusUrl
67+
$runtimeStatus = $result.runtimeStatus
68+
Write-Host "Orchestration is $runtimeStatus" -ForegroundColor Yellow
69+
70+
if ($result.runtimeStatus -eq "Completed") {
71+
$success = $true
72+
break
73+
}
74+
75+
Start-Sleep -Seconds 1
76+
$retryCount = $retryCount + 1
77+
}
78+
}
79+
80+
if ($success -eq $false) {
81+
throw "Orchestration didn't complete in time! :("
82+
}
83+
} catch {
84+
Write-Host "An error occurred:" -ForegroundColor Red
85+
Write-Host $_ -ForegroundColor Red
86+
87+
# Dump the docker logs to make debugging the issue easier
88+
Write-Host "Below are the docker logs for the app container:" -ForegroundColor Red
89+
docker logs $ContainerName
90+
91+
# Rethrow the original exception
92+
throw
93+
}
94+
95+
Write-Host "Success!" -ForegroundColor Green

samples-azure-functions/host.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"version": "2.0",
3+
"logging": {
4+
"logLevel": {
5+
"DurableTask.AzureStorage": "Warning",
6+
"DurableTask.Core": "Warning"
7+
},
8+
"applicationInsights": {
9+
"samplingSettings": {
10+
"isEnabled": false
11+
}
12+
}
13+
},
14+
"extensions": {
15+
"durableTask": {
16+
"hubName": "DFJavaSmokeTest"
17+
}
18+
},
19+
"extensionBundle": {
20+
"id": "Microsoft.Azure.Functions.ExtensionBundle.Preview",
21+
"version": "[4.*, 5.0.0)"
22+
}
23+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"IsEncrypted": false,
3+
"Values": {
4+
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
5+
"FUNCTIONS_WORKER_RUNTIME": "java"
6+
}
7+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.functions;
2+
3+
import com.microsoft.azure.functions.annotation.*;
4+
import com.microsoft.azure.functions.*;
5+
import java.util.*;
6+
7+
import com.microsoft.durabletask.DurableTaskClient;
8+
import com.microsoft.durabletask.OrchestrationRunner;
9+
import com.microsoft.durabletask.azurefunctions.DurableActivityTrigger;
10+
import com.microsoft.durabletask.azurefunctions.DurableClientContext;
11+
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
12+
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;
13+
14+
/**
15+
* Azure Durable Functions with HTTP trigger.
16+
*/
17+
public class AzureFunctions {
18+
/**
19+
* This HTTP-triggered function starts the orchestration.
20+
*/
21+
@FunctionName("StartOrchestration")
22+
public HttpResponseMessage startOrchestration(
23+
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
24+
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
25+
final ExecutionContext context) {
26+
context.getLogger().info("Java HTTP trigger processed a request.");
27+
28+
DurableTaskClient client = durableContext.getClient();
29+
String instanceId = client.scheduleNewOrchestrationInstance("Cities");
30+
context.getLogger().info("Created new Java orchestration with instance ID = " + instanceId);
31+
return durableContext.createCheckStatusResponse(request, instanceId);
32+
}
33+
34+
/**
35+
* This is the orchestrator function. The OrchestrationRunner.loadAndRun() static
36+
* method is used to take the function input and execute the orchestrator logic.
37+
*/
38+
@FunctionName("Cities")
39+
public String citiesOrchestrator(
40+
@DurableOrchestrationTrigger(name = "orchestratorRequestProtoBytes") String orchestratorRequestProtoBytes) {
41+
return OrchestrationRunner.loadAndRun(orchestratorRequestProtoBytes, ctx -> {
42+
String result = "";
43+
result += ctx.callActivity("Capitalize", "Tokyo", String.class).await() + ", ";
44+
result += ctx.callActivity("Capitalize", "London", String.class).await() + ", ";
45+
result += ctx.callActivity("Capitalize", "Seattle", String.class).await() + ", ";
46+
result += ctx.callActivity("Capitalize", "Austin", String.class).await();
47+
return result;
48+
});
49+
}
50+
51+
/**
52+
* This is the activity function that gets invoked by the orchestration.
53+
*/
54+
@FunctionName("Capitalize")
55+
public String capitalize(
56+
@DurableActivityTrigger(name = "name") String name,
57+
final ExecutionContext context) {
58+
context.getLogger().info("Capitalizing: " + name);
59+
return name.toUpperCase();
60+
}
61+
}

settings.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
include ":sdk"
22
include ":azurefunctions"
3-
include ":samples"
3+
include ":samples"
4+
include ":samples-azure-functions"

0 commit comments

Comments
 (0)