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