From 08390108c5eafbc70e600e1e690c5cd845b42679 Mon Sep 17 00:00:00 2001 From: Hal Spang Date: Thu, 18 Nov 2021 17:18:03 -0800 Subject: [PATCH] Add a workflow for invoking bindings This commit adds a workflow that utilizes two bindings to create traffic. An Azure Service Bus Queue binding is used to trigger the generic invoking of another binding. In this case, the test is setup specifically to call Azure Blob Storage. Signed-off-by: Hal Spang --- .github/workflows/binding-workflow-build.yml | 91 ++++++++++++++++++ all.sln | 6 ++ .../Controllers/BindingsController.cs | 37 ++++++++ binding-workflow/Dockerfile | 20 ++++ binding-workflow/Program.cs | 92 +++++++++++++++++++ .../Properties/launchSettings.json | 28 ++++++ binding-workflow/Startup.cs | 46 ++++++++++ binding-workflow/appsettings.Development.json | 9 ++ binding-workflow/appsettings.json | 11 +++ binding-workflow/binding-workflow.csproj | 17 ++++ common/Models/BindingInvocationMessage.cs | 22 +++++ docker-compose.yml | 6 ++ longhaul-test/azure-blob-storage-binding.yml | 19 ++++ longhaul-test/azure-cosmosdb-binding.yml | 30 ++++++ longhaul-test/azure-service-bus-binding.yml | 22 +++++ longhaul-test/binding-workflow-deploy.yml | 53 +++++++++++ .../Properties/launchSettings.json | 16 ++++ 17 files changed, 525 insertions(+) create mode 100644 .github/workflows/binding-workflow-build.yml create mode 100644 binding-workflow/Controllers/BindingsController.cs create mode 100644 binding-workflow/Dockerfile create mode 100644 binding-workflow/Program.cs create mode 100644 binding-workflow/Properties/launchSettings.json create mode 100644 binding-workflow/Startup.cs create mode 100644 binding-workflow/appsettings.Development.json create mode 100644 binding-workflow/appsettings.json create mode 100644 binding-workflow/binding-workflow.csproj create mode 100644 common/Models/BindingInvocationMessage.cs create mode 100644 longhaul-test/azure-blob-storage-binding.yml create mode 100644 longhaul-test/azure-cosmosdb-binding.yml create mode 100644 longhaul-test/azure-service-bus-binding.yml create mode 100644 longhaul-test/binding-workflow-deploy.yml create mode 100644 pubsub-workflow/Properties/launchSettings.json 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