diff --git a/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj b/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj index 1ac631dd..0eef047b 100644 --- a/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj +++ b/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHigh.csproj @@ -1,22 +1,22 @@ - net6.0 + net8.0 v4 - - - true + Exe + enabled - - + - - PreserveNewest - PreserveNewest Never - + + + + + + \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHighFn.cs b/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHighFn.cs index 5ce7ad67..0544fb7a 100644 --- a/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHighFn.cs +++ b/priority-queue/PriorityQueueConsumerHigh/PriorityQueueConsumerHighFn.cs @@ -1,14 +1,16 @@ -using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; +using Microsoft.Azure.Functions.Worker; namespace PriorityQueueConsumerHigh { - public static class PriorityQueueConsumerHighFn + public class PriorityQueueConsumerHighFn(ILogger logger) { - [FunctionName("HighPriorityQueueConsumerFunction")] - public static void Run([ServiceBusTrigger("messages", "highPriority", Connection = "ServiceBusConnection")]string highPriorityMessage, ILogger log) + private readonly ILogger _logger = logger; + + [Function("HighPriorityQueueConsumerFunction")] + public void Run([ServiceBusTrigger("messages", "highPriority", Connection = "ServiceBusConnection")] string highPriorityMessage) { - log.LogInformation($"C# ServiceBus topic trigger function processed message: {highPriorityMessage}"); + _logger.LogInformation($"C# ServiceBus topic trigger function processed message: {highPriorityMessage}"); } } -} +} \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerHigh/Program.cs b/priority-queue/PriorityQueueConsumerHigh/Program.cs new file mode 100644 index 00000000..cd97ae1f --- /dev/null +++ b/priority-queue/PriorityQueueConsumerHigh/Program.cs @@ -0,0 +1,7 @@ +using Microsoft.Extensions.Hosting; + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .Build(); + +host.Run(); \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerHigh/host.json b/priority-queue/PriorityQueueConsumerHigh/host.json index beb2e402..5df170b6 100644 --- a/priority-queue/PriorityQueueConsumerHigh/host.json +++ b/priority-queue/PriorityQueueConsumerHigh/host.json @@ -1,11 +1,12 @@ { - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true } + } } \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerHigh/local.settings.json b/priority-queue/PriorityQueueConsumerHigh/local.settings.json deleted file mode 100644 index 80410be8..00000000 --- a/priority-queue/PriorityQueueConsumerHigh/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "ServiceBusConnection__fullyQualifiedNamespace": ".servicebus.windows.net" - } -} \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json b/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json new file mode 100644 index 00000000..8cbbeb91 --- /dev/null +++ b/priority-queue/PriorityQueueConsumerHigh/local.settings.template.json @@ -0,0 +1,8 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_CONNECTION}" + } +} \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj b/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj index 6ce569f0..0eef047b 100644 --- a/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj +++ b/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLow.csproj @@ -1,23 +1,22 @@ - net6.0 + net8.0 v4 - 736bb6a2-68b4-463b-a8fb-3a90cba7cd4f - - - true + Exe + enabled - - + - - PreserveNewest - PreserveNewest Never - + + + + + + \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLowFn.cs b/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLowFn.cs index 36c1c593..a9213d2c 100644 --- a/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLowFn.cs +++ b/priority-queue/PriorityQueueConsumerLow/PriorityQueueConsumerLowFn.cs @@ -1,14 +1,16 @@ -using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; +using Microsoft.Azure.Functions.Worker; namespace PriorityQueueConsumerLow { - public static class PriorityQueueConsumerLowFn + public class PriorityQueueConsumerLowFn(ILogger logger) { - [FunctionName("LowPriorityQueueConsumerFunction")] - public static void Run([ServiceBusTrigger("messages", "lowPriority", Connection = "ServiceBusConnection")]string lowPriorityMessage, ILogger log) + private readonly ILogger _logger = logger; + + [Function("LowPriorityQueueConsumerFunction")] + public void Run([ServiceBusTrigger("messages", "lowPriority", Connection = "ServiceBusConnection")] string lowPriorityMessage) { - log.LogInformation($"C# ServiceBus topic trigger function processed message: {lowPriorityMessage}"); + _logger.LogInformation($"C# ServiceBus topic trigger function processed message: {lowPriorityMessage}"); } } -} +} \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/Program.cs b/priority-queue/PriorityQueueConsumerLow/Program.cs new file mode 100644 index 00000000..cd97ae1f --- /dev/null +++ b/priority-queue/PriorityQueueConsumerLow/Program.cs @@ -0,0 +1,7 @@ +using Microsoft.Extensions.Hosting; + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .Build(); + +host.Run(); \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/host.json b/priority-queue/PriorityQueueConsumerLow/host.json index beb2e402..5df170b6 100644 --- a/priority-queue/PriorityQueueConsumerLow/host.json +++ b/priority-queue/PriorityQueueConsumerLow/host.json @@ -1,11 +1,12 @@ { - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true } + } } \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/local.settings.json b/priority-queue/PriorityQueueConsumerLow/local.settings.json deleted file mode 100644 index 80410be8..00000000 --- a/priority-queue/PriorityQueueConsumerLow/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "ServiceBusConnection__fullyQualifiedNamespace": ".servicebus.windows.net" - } -} \ No newline at end of file diff --git a/priority-queue/PriorityQueueConsumerLow/local.settings.template.json b/priority-queue/PriorityQueueConsumerLow/local.settings.template.json new file mode 100644 index 00000000..8cbbeb91 --- /dev/null +++ b/priority-queue/PriorityQueueConsumerLow/local.settings.template.json @@ -0,0 +1,8 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_CONNECTION}" + } +} \ No newline at end of file diff --git a/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj b/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj index 1ac631dd..81ebc955 100644 --- a/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj +++ b/priority-queue/PriorityQueueSender/PriorityQueueSender.csproj @@ -1,22 +1,29 @@ - - - net6.0 - v4 - - - true - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - + + + net8.0 + v4 + Exe + enabled + enable + + + + + + + + + + PreserveNewest + Never + + + + + PreserveNewest + + + + + diff --git a/priority-queue/PriorityQueueSender/PriorityQueueSenderFn.cs b/priority-queue/PriorityQueueSender/PriorityQueueSenderFn.cs index 9429b6d6..405c710e 100644 --- a/priority-queue/PriorityQueueSender/PriorityQueueSenderFn.cs +++ b/priority-queue/PriorityQueueSender/PriorityQueueSenderFn.cs @@ -1,31 +1,34 @@ using System; using System.Threading.Tasks; using Azure.Messaging.ServiceBus; -using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; +using Microsoft.Azure.Functions.Worker; namespace PriorityQueueSender { - public static class PriorityQueueSenderFn + public class PriorityQueueSenderFn(ILogger logger, ServiceBusClient client) { - [FunctionName("PriorityQueueSenderFunction")] - public static async Task Run( - [TimerTrigger("0,30 * * * * *")] TimerInfo myTimer, - [ServiceBus("messages", Connection = "ServiceBusConnection")] IAsyncCollector collector ) + private readonly ILogger _logger = logger; + private readonly ServiceBusClient _client = client; + + [Function("PriorityQueueSenderFunction")] + public async Task Run([TimerTrigger("0,30 * * * * *")] TimerInfo myTimer) { + var sender = _client.CreateSender("messages"); for (int i = 0; i < 10; i++) { var messageId = Guid.NewGuid().ToString(); var lpMessage = new ServiceBusMessage() { MessageId = messageId }; lpMessage.ApplicationProperties["Priority"] = Priority.Low; lpMessage.Body = BinaryData.FromString($"Low priority message with Id: {messageId}"); - await collector.AddAsync(lpMessage); + await sender.SendMessageAsync(lpMessage); messageId = Guid.NewGuid().ToString(); var hpMessage = new ServiceBusMessage() { MessageId = messageId }; hpMessage.ApplicationProperties["Priority"] = Priority.High; hpMessage.Body = BinaryData.FromString($"High priority message with Id: {messageId}"); - await collector.AddAsync(hpMessage); + await sender.SendMessageAsync(hpMessage); } } } -} +} \ No newline at end of file diff --git a/priority-queue/PriorityQueueSender/Program.cs b/priority-queue/PriorityQueueSender/Program.cs new file mode 100644 index 00000000..e2037b64 --- /dev/null +++ b/priority-queue/PriorityQueueSender/Program.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Azure.Messaging.ServiceBus; +using Azure.Identity; +using System; +using Microsoft.Extensions.Azure; + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureAppConfiguration((hostingContext, config) => + { + config.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true); + }) + .ConfigureServices(services => + { + var configuration = services.BuildServiceProvider().GetRequiredService(); + + services.AddSingleton(configuration); + + services.AddAzureClients(builder => + { + builder.AddServiceBusClientWithNamespace(Environment.GetEnvironmentVariable("ServiceBusConnection__fullyQualifiedNamespace")) + .WithCredential(new DefaultAzureCredential()); + }); + }) + .Build(); + +host.Run(); \ No newline at end of file diff --git a/priority-queue/PriorityQueueSender/host.json b/priority-queue/PriorityQueueSender/host.json index beb2e402..5df170b6 100644 --- a/priority-queue/PriorityQueueSender/host.json +++ b/priority-queue/PriorityQueueSender/host.json @@ -1,11 +1,12 @@ { - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true } + } } \ No newline at end of file diff --git a/priority-queue/PriorityQueueSender/local.settings.json b/priority-queue/PriorityQueueSender/local.settings.json deleted file mode 100644 index 445c47d3..00000000 --- a/priority-queue/PriorityQueueSender/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "ServiceBusConnection__fullyQualifiedNamespace": "mgransb1.servicebus.windows.net" - } -} \ No newline at end of file diff --git a/priority-queue/PriorityQueueSender/local.settings.template.json b/priority-queue/PriorityQueueSender/local.settings.template.json new file mode 100644 index 00000000..8cbbeb91 --- /dev/null +++ b/priority-queue/PriorityQueueSender/local.settings.template.json @@ -0,0 +1,8 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "ServiceBusConnection__fullyQualifiedNamespace": "{SERVICE_BUS_CONNECTION}" + } +} \ No newline at end of file diff --git a/priority-queue/Readme.md b/priority-queue/Readme.md index 01e0ef99..e7c9ceaa 100644 --- a/priority-queue/Readme.md +++ b/priority-queue/Readme.md @@ -1,100 +1,125 @@ -# Priority Queue Pattern +# Priority Queue pattern example -This document describes the Priority Queue Pattern example from the guide [Cloud Design Patterns](http://aka.ms/Cloud-Design-Patterns). +This directory contains an example of the [Priority Queue pattern](https://learn.microsoft.com/azure/architecture/patterns/priority-queue). -## System Requirements +This example demonstrates how priority queues can be implemented by using Service Bus topics and subscriptions. A time-triggered Azure Function is responsible for sending messages to a topic, each with a priority assigned. The receiving Azure Functions read messages from subscriptions that have the corresponding priority. In this example, the _PriorityQueueConsumerHigh_ Azure function can scale out to 200 instances, while the _PriorityQueueConsumerLow_ function runs only with one instance. This example simulates high priority messages being read from the queue more urgently than low priority messages. -* Microsoft .NET 6 -* Microsoft Visual Studio 2019 or later version -* Azure Functions Core Tools version 4x +This example also demonstrates operational aspects of applications running on Azure. Monitoring tools need to be used in order to understand how the sample works. Azure Functions in the solution **must** be configured to use the diagnostics mechanism. If not, you will not see the trace information generated by the example. -## Before you start +## :rocket: Deployment guide -Ensure that you have installed all of the software prerequisites. +Install the prerequisites and follow the steps to deploy and run an example of the Priority Queue pattern. -The example demonstrates operational aspects of applications running in Windows Azure. Therefore, you will need to use the monitoring tools in order to understand how the code sample works. You **must** ensure that the Azure Functions in the solution are configured to use the diagnostics mechanism. If not, you will not see the trace information generated by the example. +### Prerequisites -## About the Example +- Permission to create a new resource group and resources in an [Azure subscription](https://azure.com/free). +- [Git](https://git-scm.com/downloads) +- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [Azure Functions Core Tools v4.x](https://learn.microsoft.com/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools) -This example shows how you can implement priority queues by using Service Bus Topics and Subscriptions. A timer triggered Azure Function is responsible for sending messages to a topic. It assigns a priority to each message. The receiving Azure Functions read messages from subscriptions that have the corresponding priority. In the example, the PriorityQueueConsumerHigh Azure function can scale out to 200 instances, whereas the PriorityQueueConsumerLow worker runs only with one instance. This simulates high priority messages being read from the queue more urgently than low priority messages. +### Steps -## Running the Example +1. Clone the repository -You can either run this example locally from Visual Studio or you can run it by deploying it to Azure. + Open a terminal, clone the repository, and navigate to the `priority-queue` directory. -* From the Azure Portal, provision an Azure Service Bus Namespace. -* Once the Service Bus Namespace is created, add a new topic to it, name it "messages" (leave all the properties default). -* Once the topic is created, add a new subscription to it, name it "highPriority", set "10" as Max delivery count. -* Add a new filter of type "SqlFilter" to the subscription, name it "priorityFilter". -* Set the expression __Priority = 'highpriority'__ in the filter body. -* Click on "Save changes". -* Add another subscription to the topic, name it "lowPriority", set "10" as Max delivery count. -* Add a new filter of type "SqlFilter" to the subscription, name it "priorityFilter". -* Set the expression __Priority = 'lowpriority'__ in the filter body. -* Click on "Save changes". + ```shell + git clone https://github.com/mspnp/cloud-design-patterns.git + cd cloud-design-patterns + cd priority-queue + ``` -* Start Visual Studio. -* Open the solution you want to explore from the subfolders where you downloaded the examples. -* Edit the local.settings.json file in all the projects and change the ServiceBusConnection__fullyQualifiedNamespace setting by changing the placeholder "" to your Azure Service Bus Namespace name. +1. Log into Azure and create an empty resource group. -* If you want to run the example in the local Windows Azure emulator: - * Set the PriorityQueueSender project as startup. - * Press F5 in Visual Studio to start the example running. The function will start sending messages to the topic, every 30 seconds. - * Stop the execution, change the startup project to either the PriorityQueueConsumerHigh or PriorityQueueConsumerLow - * Press F5 in Visual Studio to start the execution of the consumer function - * In the Azure Functions Core Tools Console, you can view the diagnostic information generated by Trace statements in the code. + Create an empty resource group to hold the resources for this example. The location you select in the resource group creation command below is the Azure region that your resources will be deployed in; modify as needed. -* If you want to run the example on Azure: - * On every function project, right click and select publish. - * Select "Azure" as publishing target. - * Select "Azure Function App" as specific target. - * In the Functions instance step, select you Azure Function app or create a new one using the link button. - * Since your app settings are different you need to deploy every Azure Function in a separate Azure Funcion App. - * You can select an existing resource group and storage account or create new ones. - * In "Hosting" section, click on the the three dots (...) in the upper right corner. - * Select "Manage Azure App Service Settings". - * You need to add these two settings: + ```bash + az login + az account set -s - - ServiceBusConnection__fullyQualifiedNamespace: + LOCATION=eastus2 + RESOURCE_GROUP_NAME=rg-priority-queue-${LOCATION} + az group create -n $RESOURCE_GROUP_NAME -l $LOCATION + ``` - Set the value to: +1. Deploy the supporting Azure resources. - .servicebus.windows.net + ```bash + CURRENT_USER_OBJECT_ID=$(az ad signed-in-user show -o tsv --query id) + SERVICE_BUS_NAMESPACE_NAME="sbns-priority-queue-$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | fold -w 7 | head -n 1)" - Replacing the placeholder with you Azure Service Bus Namespace name. + # This takes about two minute + az deployment group create -n deploy-priority-queue -f bicep/main.bicep -g "${RESOURCE_GROUP_NAME}" -p queueNamespaces=$SERVICE_BUS_NAMESPACE_NAME principalId=$CURRENT_USER_OBJECT_ID + ``` - * Once the Functions are deployed you need to restrict the maximum number of instances your app service consumption can scale out to: +1. Configure the samples to use the created Azure resources. - * From the portal go to the Function App that contains the "PriorityQueueConsumerLow" Azure Function - * Navigate to Scale Out on the left menu - * On the App Scale Out dialog set the "Enforce Scale Out Limit" to "Yes" - * Set the Maximum Scale Out Limit to 1 instance - - You don't need to modify these settings for the Function App containing the "PriorityQueueConsumerHigh" Azure Function since the default setting is 200; this ensures that high priority messages are read from the queue more quickly than low priority messages. + ```bash + # Retrieve the primary connection string for the Service Bus namespace. + SERVICE_BUS_CONNECTION="${SERVICE_BUS_NAMESPACE_NAME}.servicebus.windows.net" - * Now you need to configure the managed identities role assignments: + sed "s|{SERVICE_BUS_CONNECTION}|${SERVICE_BUS_CONNECTION}|g" ./PriorityQueueSender/local.settings.template.json > ./PriorityQueueSender/local.settings.json + sed "s|{SERVICE_BUS_CONNECTION}|${SERVICE_BUS_CONNECTION}|g" ./PriorityQueueConsumerHigh/local.settings.template.json > ./PriorityQueueConsumerHigh/local.settings.json + sed "s|{SERVICE_BUS_CONNECTION}|${SERVICE_BUS_CONNECTION}|g" ./PriorityQueueConsumerLow/local.settings.template.json > ./PriorityQueueConsumerLow/local.settings.json + ``` - In the Azure portal, navigate to the Azure Service Bus Namespace that was provisioned in the first step. - Select Access Control (IAM). This is where you can view and configure who has access to the resource. - Click Add and select add role assignment. - From the list, select "Azure Service Bus Data Sender", click Next - In "Assign access to", radio button list, select "Managed identity" - Click on "Select members", the "Select Manage identities" dialog will show up - In the Managed Identity dropdown list select "Function App" - Find the PriorityQueueSender function app click on it - Click "Select" - On the main dialog, click "Review + Assign" +1. [Run Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite#run-azurite) blob storage emulation service. - For the Azure Consumer Function Apps, repeat the process but in this case use the role - "Azure Service Bus Data Reader" + > The local storage emulator is required as an Azure Storage account is a required "backing resource" for Azure Functions. - * Once the Functions are deployed you can configure monitoring by following these steps: +1. Launch the Function PriorityQueueSender to generate Low and High messages. - - From the Azure Portal, go to the Function App Service - - Click on "Functions" and select the azure function - - From the "developer" left menu click on "Monitor" - - Turn on application insights - - Refresh the screen - - Once refreshed you will see two tabs, "invocation" and "logs" - - From the invocations tab you can see the twenty most recent function invocation traces. For more advanced analysis, run the query in Application Insights. - - From the logs tab you can see the logging information that your functions are sending. \ No newline at end of file + ```bash + cd ./PriorityQueueSender + func start + ``` + +1. In a new terminal, launch the Function PriorityQueueConsumerLow to consume messages. + + ```bash + cd ./PriorityQueueConsumerLow + func start -p 15000 + ``` + + > Please note: For demo purposes, the sample application will write content to the the screen. + +1. In a new terminal, launch the Function PriorityQueueConsumerHigh to consume messages. + + ```bash + cd ./PriorityQueueConsumerHigh + func start -p 15001 + ``` + + > Please note: For demo purposes, the sample application will write content to the the screen. + +## Deploy the example to Azure (Optional) + +To deploy the example to Azure, you need to publish each Azure Functions to Azure. You can do so from Visual Studio by right clicking each function and selecting `Publish` from the menu. Use the same resource group and region from earlier. Be sure to enable Application Insights. + +Once each function is published, a new App Setting must be added to store the connection string to the Service Bus namespace. This is the same value that was used in the `SERVICE_BUS_CONNECTION_STRING` variable in the previous steps. + +For each function, run the following: + +```bash +az webapp config appsettings set -n -g $RESOURCE_GROUP_NAME --settings ServiceBusConnection__fullyQualifiedNamespace=$SERVICE_BUS_CONNECTION +``` + +Once the functions are deployed you need to restrict the maximum number of instances the `PriorityQueueConsumerLow` function can scale out to. + +From the Azure portal: + +- Visit the Function App that contains `PriorityQueueConsumerLow` +- Navigate to Scale Out on the left menu +- On the App Scale Out dialog, set the `Enforce Scale Out Limit` to `Yes` +- Set the `Maximum Scale Out Limit` to `1` instance + +Once the functions are deployed you can visit Application Insights to view the most recent activity for each function. + +## :broom: Clean up resources + +Be sure to delete Azure resources when not using them. Since all resources were deployed into a new resource group, you can simply delete the resource group. + +```bash +az group delete -n $RESOURCE_GROUP_NAME -y +``` diff --git a/priority-queue/bicep/main.bicep b/priority-queue/bicep/main.bicep new file mode 100644 index 00000000..ba5d4b23 --- /dev/null +++ b/priority-queue/bicep/main.bicep @@ -0,0 +1,216 @@ +targetScope = 'resourceGroup' + +@minLength(5) +@description('Location of the resources. Defaults to resource group location.') +param location string = resourceGroup().location + +@minLength(15) +@description('Service Bus Namespace Name.') +param queueNamespaces string + +@minLength(36) +@description('The guid of the principal running the valet key generation code. In Azure this would be replaced with the managed identity of the Azure Function, when running locally it will be your user.') +param principalId string + +var logAnalyticsName = 'loganalytics-${uniqueString(subscription().subscriptionId, resourceGroup().id)}' + +var senderServiceBusRole = subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39' +) // Azure Service Bus Data Sender +var receiverServiceBusRole = subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' +) // Azure Service Bus Data Receiver + +resource queueNamespacesResource 'Microsoft.ServiceBus/namespaces@2023-01-01-preview' = { + name: queueNamespaces + location: location + sku: { + name: 'Standard' + tier: 'Standard' + } + properties: { + geoDataReplication: { + maxReplicationLagDurationInSeconds: 0 + locations: [ + { + locationName: location + roleType: 'Primary' + } + ] + } + premiumMessagingPartitions: 0 + minimumTlsVersion: '1.2' + publicNetworkAccess: 'Enabled' + disableLocalAuth: false + zoneRedundant: false + } +} + +resource queueNamespacesResourceRootManageSharedAccessKey 'Microsoft.ServiceBus/namespaces/authorizationrules@2023-01-01-preview' = { + parent: queueNamespacesResource + name: 'RootManageSharedAccessKey' + location: location + properties: { + rights: [ + 'Listen' + 'Manage' + 'Send' + ] + } +} + +resource queueNamespacesResourceNetworkRules 'Microsoft.ServiceBus/namespaces/networkrulesets@2023-01-01-preview' = { + parent: queueNamespacesResource + name: 'default' + location: location + properties: { + publicNetworkAccess: 'Enabled' + defaultAction: 'Allow' + virtualNetworkRules: [] + ipRules: [] + trustedServiceAccessEnabled: false + } +} + +resource queueNamespacesResourceTopic 'Microsoft.ServiceBus/namespaces/topics@2023-01-01-preview' = { + parent: queueNamespacesResource + name: 'messages' + location: location + properties: { + maxMessageSizeInKilobytes: 256 + maxSizeInMegabytes: 1024 + requiresDuplicateDetection: false + duplicateDetectionHistoryTimeWindow: 'PT10M' + enableBatchedOperations: true + status: 'Active' + supportOrdering: true + enablePartitioning: false + enableExpress: false + } +} + +resource queueNamespacesResourceTopicHigPriority 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2023-01-01-preview' = { + parent: queueNamespacesResourceTopic + name: 'highPriority' + location: location + properties: { + isClientAffine: false + lockDuration: 'PT1M' + requiresSession: false + deadLetteringOnMessageExpiration: false + deadLetteringOnFilterEvaluationExceptions: true + maxDeliveryCount: 10 + status: 'Active' + enableBatchedOperations: true + } +} + +resource queueNamespacesResourceTopicLowPriority 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2023-01-01-preview' = { + parent: queueNamespacesResourceTopic + name: 'lowPriority' + location: location + properties: { + isClientAffine: false + lockDuration: 'PT1M' + requiresSession: false + deadLetteringOnMessageExpiration: false + deadLetteringOnFilterEvaluationExceptions: true + maxDeliveryCount: 10 + status: 'Active' + enableBatchedOperations: true + } +} + +resource queueNamespacesResourceTopicHigPriorityRules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2023-01-01-preview' = { + parent: queueNamespacesResourceTopicHigPriority + name: 'priorityFilter' + location: location + properties: { + action: {} + filterType: 'SqlFilter' + sqlFilter: { + sqlExpression: 'Priority = \'highpriority\'' + compatibilityLevel: 20 + } + } +} + +resource LqueueNamespacesResourceTopicLowPriorityRules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2023-01-01-preview' = { + parent: queueNamespacesResourceTopicLowPriority + name: 'priorityFilter' + location: location + properties: { + action: {} + filterType: 'SqlFilter' + sqlFilter: { + sqlExpression: 'Priority = \'lowpriority\'' + compatibilityLevel: 20 + } + } +} + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { + name: logAnalyticsName + location: location + properties: { + retentionInDays: 30 + features: { + searchVersion: 1 + } + sku: { + name: 'PerGB2018' + } + } +} + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: '${queueNamespacesResource.name}-diagnostic' + scope: queueNamespacesResource + properties: { + logs: [ + { + category: 'OperationalLogs' + enabled: true + retentionPolicy: { + enabled: false + days: 0 + } + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + retentionPolicy: { + enabled: false + days: 0 + } + } + ] + workspaceId: logAnalytics.id + } +} + +// Assign Role to allow sending messages to the Service Bus +resource serviceBusSenderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, principalId, 'ServiceBusSenderRole') + scope: queueNamespacesResource + properties: { + roleDefinitionId: senderServiceBusRole + principalId: principalId + principalType: 'User' // 'ServicePrincipal' if this was a managed identity + } +} + +// Assign Role to allow receiving messages from the Service Bus +resource serviceBusReceiverRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, principalId, 'ServiceBusReceiverRole') + scope: queueNamespacesResource + properties: { + roleDefinitionId: receiverServiceBusRole + principalId: principalId + principalType: 'User' // 'ServicePrincipal' if this was a managed identity + } +}