diff --git a/.github/workflows/binding-workflow-build.yml b/.github/workflows/binding-workflow-build.yml
new file mode 100644
index 00000000..7f8eaa24
--- /dev/null
+++ b/.github/workflows/binding-workflow-build.yml
@@ -0,0 +1,91 @@
+# ------------------------------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------------------------------
+
+name: build-binding-workflow
+
+on:
+ push:
+ branches:
+ - master
+ paths:
+ - 'binding-workflow/**'
+ - '.github/workflows/**'
+ pull_request:
+ branches:
+ - master
+ paths:
+ - 'binding-workflow/**'
+ - '.github/workflows/**'
+
+jobs:
+ build:
+ name: build binding-workflow
+ runs-on: ubuntu-latest
+ env:
+ APP_REGISTRY: dapriotest
+ APP_IMAGE_NAME: binding-workflow
+ # TODO: APP_VER needs to be versioned correctly
+ APP_VER: dev
+ APP_DIR: ./binding-workflow
+ ARTIFACT_DIR: ./deploy_artifact
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v2
+ - name: docker login
+ if: github.event_name != 'pull_request'
+ run: |
+ docker login -u ${{ secrets.DOCKER_REGISTRY_ID }} -p ${{ secrets.DOCKER_REGISTRY_PASS }}
+ - name: Build binding-workflow docker image
+ run: |
+ docker-compose build ${{ env.APP_IMAGE_NAME }}
+ docker tag ${{ env.APP_IMAGE_NAME }} ${{ env.APP_REGISTRY }}/${{ env.APP_IMAGE_NAME }}:${{ env.APP_VER }}
+ - name: Push binding-workflow image to dockerhub
+ if: github.event_name != 'pull_request'
+ run: |
+ docker push ${{ env.APP_REGISTRY }}/${{ env.APP_IMAGE_NAME }}:${{ env.APP_VER }}
+ - name: Copy deployment yaml to archive
+ run: |
+ mkdir -p ${{ env.ARTIFACT_DIR }}
+ cp ./longhaul-test/*.yml ${{ env.ARTIFACT_DIR }}
+ - name: Upload artifacts
+ uses: actions/upload-artifact@master
+ with:
+ name: longhaul-test
+ path: ${{ env.ARTIFACT_DIR }}
+ deploy:
+ name: deploy binding-workflow to test cluster
+ needs: build
+ if: github.event_name != 'pull_request'
+ runs-on: ubuntu-latest
+ env:
+ APP_NAMESPACE: longhaul-test
+ TEST_CLUSTER_NAME: dapr-seattle
+ TEST_RESOURCE_GROUP: dapr-test
+ ARTIFACT_DIR: ./deploy_artifact
+ steps:
+ - name: download artifacts
+ uses: actions/download-artifact@master
+ with:
+ name: longhaul-test
+ path: ${{ env.ARTIFACT_DIR }}
+ - name: Login Azure
+ run: |
+ az login --service-principal -u ${{ secrets.AZURE_LOGIN_USER }} -p ${{ secrets.AZURE_LOGIN_PASS }} --tenant ${{ secrets.AZURE_TENANT }} --output none
+ - name: Set up kubeconf for longhaul test environment
+ run: |
+ az aks get-credentials -n ${{ env.TEST_CLUSTER_NAME }} -g ${{ env.TEST_RESOURCE_GROUP }}
+ - name: Set up azure keyvault certificate secret
+ run: |
+ az keyvault secret download --vault-name longhaul-kv --name longhaul-cert --encoding base64 --file longhaul-cert.pfx
+ kubectl delete secret longhaulcert -n longhaul-test --ignore-not-found
+ kubectl create secret generic longhaulcert -n longhaul-test --from-file=longhaulcert=./longhaul-cert.pfx
+ - name: Deploy apps to longhaul test environment
+ run: |
+ kubectl apply -n ${{ env.APP_NAMESPACE }} -f ${{ env.ARTIFACT_DIR }}/secret-access-role.yml
+ kubectl apply -n ${{ env.APP_NAMESPACE }} -f ${{ env.ARTIFACT_DIR }}/secret-access-role-binding.yml
+ kubectl apply -n ${{ env.APP_NAMESPACE }} -f ${{ env.ARTIFACT_DIR }}/azure-keyvault.yml
+ kubectl apply -n ${{ env.APP_NAMESPACE }} -f ${{ env.ARTIFACT_DIR }}/azure-service-bus-binding.yml
+ kubectl apply -n ${{ env.APP_NAMESPACE }} -f ${{ env.ARTIFACT_DIR }}/azure-blob-storage-binding.yml
+ kubectl apply -n ${{ env.APP_NAMESPACE }} -f ${{ env.ARTIFACT_DIR }}/binding-workflow-deploy.yml & kubectl rollout restart -n ${{ env.APP_NAMESPACE }} deploy/binding-workflow-app
diff --git a/all.sln b/all.sln
index 86513706..1e1572fd 100644
--- a/all.sln
+++ b/all.sln
@@ -21,6 +21,8 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pubsub-workflow", "pubsub-workflow\pubsub-workflow.csproj", "{571AD477-1187-4E27-9ADB-CDD6E7370E8C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "binding-workflow", "binding-workflow\binding-workflow.csproj", "{F4B8E286-57E3-4750-849F-A49F62C1AA4C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -63,6 +65,10 @@ Global
{571AD477-1187-4E27-9ADB-CDD6E7370E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{571AD477-1187-4E27-9ADB-CDD6E7370E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{571AD477-1187-4E27-9ADB-CDD6E7370E8C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F4B8E286-57E3-4750-849F-A49F62C1AA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F4B8E286-57E3-4750-849F-A49F62C1AA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F4B8E286-57E3-4750-849F-A49F62C1AA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F4B8E286-57E3-4750-849F-A49F62C1AA4C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/binding-workflow/Controllers/BindingsController.cs b/binding-workflow/Controllers/BindingsController.cs
new file mode 100644
index 00000000..a3d48c69
--- /dev/null
+++ b/binding-workflow/Controllers/BindingsController.cs
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+using Dapr.Client;
+using Dapr.Tests.Common.Models;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace BindingWorkflow
+{
+ [ApiController]
+ public class BindingsController : ControllerBase
+ {
+ ///
+ /// Handle a message from the Azure Service Bus input binding.
+ ///
+ [HttpPost("/longhaul-invoke-binding")]
+ public async Task HandleBinding([FromBody] BindingInvocationMessage message, [FromServices] DaprClient client)
+ {
+ Console.WriteLine($"Invoking binding {message.TargetBinding} with operation \"{message.TargetOperation}\" {message.InvocationCount} times.");
+ for (var i = 0; i < message.InvocationCount; i++)
+ {
+ var data = new Dictionary(message.Data);
+ if (message.Metadata.ContainsKey("autoGenId")) {
+ data.Add("id", Guid.NewGuid().ToString());
+ }
+ await client.InvokeBindingAsync(message.TargetBinding, message.TargetOperation, data);
+ }
+ Console.WriteLine("Finished invoking binding.");
+ return Ok();
+ }
+ }
+}
\ No newline at end of file
diff --git a/binding-workflow/Dockerfile b/binding-workflow/Dockerfile
new file mode 100644
index 00000000..733b9e9c
--- /dev/null
+++ b/binding-workflow/Dockerfile
@@ -0,0 +1,20 @@
+# ------------------------------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------------------------------
+
+FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
+WORKDIR /app
+EXPOSE 3000 9988
+
+FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
+WORKDIR /app
+
+COPY . ./
+RUN dotnet restore binding-workflow/*.csproj
+RUN dotnet publish binding-workflow/*.csproj -c Release -o /out
+
+FROM base AS final
+WORKDIR /app
+COPY --from=build-env /out ./
+ENTRYPOINT ["dotnet", "binding-workflow.dll"]
\ No newline at end of file
diff --git a/binding-workflow/Program.cs b/binding-workflow/Program.cs
new file mode 100644
index 00000000..d05ff1f9
--- /dev/null
+++ b/binding-workflow/Program.cs
@@ -0,0 +1,92 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using Dapr.Client;
+using Dapr.Tests.Common.Models;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Prometheus;
+
+namespace BindingWorkflow
+{
+ public class Program
+ {
+ private static readonly string AzureServiceBusBinding = "longhaul-invoke-binding";
+ private static readonly string BlobStorageBinding = "longhaul-blob-binding";
+ private static readonly string CosmosDBBinding = "longhaul-cosmosdb-binding";
+ private static readonly string CreateOperation = "create";
+
+ public static void Main(string[] args)
+ {
+ Console.WriteLine("Starting bindings workflow.");
+
+ var server = new MetricServer(port: 9988);
+ server.Start();
+
+ var host = CreateHostBuilder(args).Build();
+
+ // Invoke the blob storage output binding 200 times every 5 minutes (2400 messages per hour).
+ var blobPublishTimer = StartInvokingBinding(300, BlobStorageBinding, CreateOperation, 200, new Dictionary());
+ var cosmosDBPublishTimer = StartInvokingBinding(300, CosmosDBBinding, CreateOperation, 200, new Dictionary() {
+ { "autoGenId", "true"}
+ });
+
+ host.Run();
+
+ Console.WriteLine("Exiting bindings workflow.");
+
+ blobPublishTimer.Dispose();
+ cosmosDBPublishTimer.Dispose();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureLogging((hostingContext, config) =>
+ {
+ config.ClearProviders();
+ config.AddConsole();
+
+ })
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ var appSettings = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile($"appsettings.json", optional: true, reloadOnChange: true)
+ .AddCommandLine(args)
+ .Build();
+
+ webBuilder.UseStartup()
+ .UseUrls(urls: $"http://*:{appSettings["DaprHTTPAppPort"]}");
+ });
+
+ static internal Timer StartInvokingBinding(int periodInSeconds, string targetBinding, string targetOperation, int invocationCount, Dictionary metadata)
+ {
+ var client = new DaprClientBuilder().Build();
+
+ return new Timer(async (state) =>
+ {
+ Console.WriteLine($"Invoking binding to trigger {targetBinding} binding.");
+ var data = new Dictionary();
+ data.Add("timestamp", DateTime.Now.ToLongTimeString());
+ data.Add("pk", Guid.NewGuid().ToString());
+ var request = new BindingInvocationMessage
+ {
+ TargetBinding = targetBinding,
+ TargetOperation = targetOperation,
+ InvocationCount = invocationCount,
+ Data = data,
+ Metadata = metadata,
+ };
+ await client.InvokeBindingAsync(AzureServiceBusBinding, CreateOperation, request);
+ }, null, TimeSpan.FromSeconds(new Random().Next(5, 10)), TimeSpan.FromSeconds(periodInSeconds));
+ }
+ }
+}
diff --git a/binding-workflow/Properties/launchSettings.json b/binding-workflow/Properties/launchSettings.json
new file mode 100644
index 00000000..7dcfdd40
--- /dev/null
+++ b/binding-workflow/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:19822",
+ "sslPort": 44364
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "storage_binding": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/binding-workflow/Startup.cs b/binding-workflow/Startup.cs
new file mode 100644
index 00000000..cdc3516e
--- /dev/null
+++ b/binding-workflow/Startup.cs
@@ -0,0 +1,46 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace BindingWorkflow
+{
+ public class Startup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddDaprClient();
+ services.AddControllers();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseRouting();
+
+ app.UseCloudEvents();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ }
+ }
+}
diff --git a/binding-workflow/appsettings.Development.json b/binding-workflow/appsettings.Development.json
new file mode 100644
index 00000000..dba68eb1
--- /dev/null
+++ b/binding-workflow/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/binding-workflow/appsettings.json b/binding-workflow/appsettings.json
new file mode 100644
index 00000000..2079f509
--- /dev/null
+++ b/binding-workflow/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+ "DaprHTTPAppPort": "3000"
+}
diff --git a/binding-workflow/binding-workflow.csproj b/binding-workflow/binding-workflow.csproj
new file mode 100644
index 00000000..95c9a976
--- /dev/null
+++ b/binding-workflow/binding-workflow.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/Models/BindingInvocationMessage.cs b/common/Models/BindingInvocationMessage.cs
new file mode 100644
index 00000000..d89ad640
--- /dev/null
+++ b/common/Models/BindingInvocationMessage.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace Dapr.Tests.Common.Models
+{
+ public class BindingInvocationMessage
+ {
+ [Required]
+ public string TargetBinding { get; set; }
+ [Required]
+ public string TargetOperation { get; set; }
+ [Required]
+ public int InvocationCount { get; set; }
+ public Dictionary Data { get; set; }
+ public Dictionary Metadata { get; set; }
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 6eddb0fc..6c779d8a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,12 @@
version: '3.3'
services:
+ binding-workflow:
+ image: binding-workflow
+ build:
+ context: .
+ dockerfile: binding-workflow/Dockerfile
+
feed-generator:
image: feed-generator
build:
diff --git a/longhaul-test/azure-blob-storage-binding.yml b/longhaul-test/azure-blob-storage-binding.yml
new file mode 100644
index 00000000..c24c4402
--- /dev/null
+++ b/longhaul-test/azure-blob-storage-binding.yml
@@ -0,0 +1,19 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: longhaul-blob-binding
+ namespace: longhaul-test
+spec:
+ type: bindings.azure.blobstorage
+ version: v1
+ metadata:
+ - name: storageAccount
+ value: longhaulstorageaccount
+ - name: storageAccessKey
+ secretKeyRef:
+ name: storage-key
+ key: storage-key
+ - name: container
+ value: longhaul-container
+auth:
+ secretStore: longhaul-kv
\ No newline at end of file
diff --git a/longhaul-test/azure-cosmosdb-binding.yml b/longhaul-test/azure-cosmosdb-binding.yml
new file mode 100644
index 00000000..5da061e3
--- /dev/null
+++ b/longhaul-test/azure-cosmosdb-binding.yml
@@ -0,0 +1,30 @@
+# ------------------------------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------------------------------
+
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: longhaul-cosmosdb-binding
+ namespace: longhaul-test
+spec:
+ type: bindings.azure.cosmosdb
+ version: v1
+ metadata:
+ - name: url
+ value: https://longhaul-cosmos.documents.azure.com:443/
+ - name: masterKey
+ secretKeyRef:
+ name: cosmos-master-key
+ key: cosmos-master-key
+ - name: database
+ value: longhauldb
+ - name: collection
+ value: longhaulcontainer
+ - name: partitionKey
+ value: pk
+auth:
+ secretStore: longhaul-kv
+scopes:
+ - binding-workflow
\ No newline at end of file
diff --git a/longhaul-test/azure-service-bus-binding.yml b/longhaul-test/azure-service-bus-binding.yml
new file mode 100644
index 00000000..4e4078c1
--- /dev/null
+++ b/longhaul-test/azure-service-bus-binding.yml
@@ -0,0 +1,22 @@
+# ------------------------------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------------------------------
+
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: longhaul-invoke-binding
+ namespace: longhaul-test
+spec:
+ type: bindings.azure.servicebusqueues
+ version: v1
+ metadata:
+ - name: queueName
+ value: longhaul-queue
+ - name: connectionString
+ secretKeyRef:
+ name: sb-conn
+ key: sb-conn
+auth:
+ secretStore: longhaul-kv
\ No newline at end of file
diff --git a/longhaul-test/binding-workflow-deploy.yml b/longhaul-test/binding-workflow-deploy.yml
new file mode 100644
index 00000000..c4488a27
--- /dev/null
+++ b/longhaul-test/binding-workflow-deploy.yml
@@ -0,0 +1,53 @@
+# ------------------------------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------------------------------
+
+kind: Service
+apiVersion: v1
+metadata:
+ name: binding-workflow
+ labels:
+ app: binding-workflow
+ namespace: longhaul-test
+spec:
+ selector:
+ app: binding-workflow
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 9988
+ type: LoadBalancer
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: binding-workflow-app
+ labels:
+ app: binding-workflow
+ namespace: longhaul-test
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: binding-workflow
+ template:
+ metadata:
+ labels:
+ app: binding-workflow
+ annotations:
+ dapr.io/enabled: "true"
+ dapr.io/app-id: "binding-workflow"
+ dapr.io/app-port: "3000"
+ dapr.io/log-as-json: "true"
+ prometheus.io/scrape: 'true'
+ prometheus.io/port: '9988'
+ spec:
+ containers:
+ - name: binding-workflow
+ image: halspang/binding-workflow:dev
+ ports:
+ - containerPort: 3000
+ - containerPort: 9988
+ imagePullPolicy: Always
diff --git a/pubsub-workflow/Properties/launchSettings.json b/pubsub-workflow/Properties/launchSettings.json
new file mode 100644
index 00000000..082becf0
--- /dev/null
+++ b/pubsub-workflow/Properties/launchSettings.json
@@ -0,0 +1,16 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true
+ },
+ "profiles": {
+ "pubsub-workflow": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15557",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file