From 6b5e82f01b679cdfe2c9adca6a14ce49a7427a7e Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Wed, 7 Sep 2022 14:20:41 -0700 Subject: [PATCH] Adding workarounds for broken func templates --- .../README.md | 72 ++++++++++++------- .../iot-hub-trigger/__init__.py | 5 +- .../iot-hub-trigger/function.json | 6 +- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md index 9785c78..a5e634e 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md @@ -292,21 +292,19 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- This file will contain the following code: ```python - from typing import List import logging + import azure.functions as func - - def main(events: List[func.EventHubEvent]): - for event in events: - logging.info('Python EventHub trigger processed an event: %s', - event.get_body().decode('utf-8')) - ``` - The core of the trigger is the `main` function. It is this function that is called with the events from the IoT Hub. This function has a parameter called `events` that contains a list of `EventHubEvent`. Each event in this list is a message sent to IoT Hub, along with properties that are the same as the annotations you saw in the last lesson. - This trigger processes a list of events, rather than individual events. When you first run the trigger it wil process any unprocessed events on the IoT Hub (remember that messages are stored for a while so they are not lost if your application code is offline). After this it will generally process a list containing only one event, unless a lot of events are sent to the Hub in a short space of time. + def main(event: func.EventHubEvent): + logging.info('Python EventHub trigger processed an event: %s', + event.get_body().decode('utf-8')) + ``` + + The core of the trigger is the `main` function. It is this function that is called with the events from the IoT Hub. This function has a parameter called `event` that contains an `EventHubEvent`. Every time a message is sent to IoT Hub, this function is called passing that message as the `event`, along with properties that are the same as the annotations you saw in the last lesson. - The core of this function loops through the list and logs the events. + The core of this function logs the event. * `function.json` - this contains configuration for the trigger. The main configuration is in a section called `bindings`. A binding is the term for a connection between Azure Functions and other Azure services. This function has an input binding to an event hub - it connects to an event hub and receives data. @@ -323,6 +321,12 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- > 💁 The connection string cannot be stored in the `function.json` file, it has to be read from the settings. This is to stop you accidentally exposing your connection string. +1. Due to [a bug in the Azure Functions template](https://github.com/Azure/azure-functions-templates/issues/1250), the `function.json` has an incorrect value for the `cardinality` field. Update this field from `many` to `one`: + + ```json + "cardinality": "one", + ``` + 1. Update the value of `"connection"` in the `function.json` file to point to the new value you added to the `local.settings.json` file: ```json @@ -331,6 +335,12 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- > 💁 Remember - this needs to point to the setting, not contain the actual connection string. +1. The connection string contains the `eventHubName` value, so the value for this in the `function.json` file needs to be cleared. Update this value to an empty string: + + ```json + "eventHubName": "", + ``` + ### Task - run the event trigger 1. Make sure you are not running the IoT Hub event monitor. If this is running at the same time as the functions app, the functions app will not be able to connect and consume events. @@ -368,20 +378,38 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- Each call to the function will be surrounded by a `Executing 'Functions.iot-hub-trigger'`/`Executed 'Functions.iot-hub-trigger'` block in the output, so you can how many messages were processed in each function call. - > If you get the following error: - - ```output - The listener for function 'Functions.iot-hub-trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused. - ``` - - Then check Azurite is running and you have set the `AzureWebJobsStorage` in the `local.settings.json` file to `UseDevelopmentStorage=true`. - 1. Make sure your IoT device is running, You will see new soil moisture messages appearing in the Functions app. 1. Stop and restart the Functions app. You will see that it won't process messages previous messages again, it will only process new messages. > 💁 VS Code also supports debugging your Functions. You can set break points by clicking on the border by the start of each line of code, or putting the cursor on a line of code and selecting *Run -> Toggle breakpoint*, or pressing `F9`. You can launch the debugger by selecting *Run -> Start debugging*, pressing `F5`, or selecting the *Run and debug* pane and selecting the **Start debugging** button. By doing this you can see the details of the events being processed. +#### Troubleshooting + +* If you get the following error: + + ```output + The listener for function 'Functions.iot-hub-trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused. + ``` + + Check Azurite is running and you have set the `AzureWebJobsStorage` in the `local.settings.json` file to `UseDevelopmentStorage=true`. + +* If you get the following error: + + ```output + System.Private.CoreLib: Exception while executing function: Functions.iot-hub-trigger. System.Private.CoreLib: Result: Failure Exception: AttributeError: 'list' object has no attribute 'get_body' + ``` + + Check that you have set the `cardinality` in the `function.json` file to `one`. + +* If you get the following error: + + ```output + Azure.Messaging.EventHubs: The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both. Please verify that your connection string does not have the `EntityPath` token if you are passing an explicit Event Hub name. (Parameter 'connectionString'). + ``` + + Check that you have set the `eventHubName` in the `function.json` file to an empty string. + ## Send direct method requests from serverless code So far your Functions app is listening to messages from the IoT Hub using the Event Hub compatible end point. You now need to send commands to the IoT device. This is done by using a different connection to the IoT Hub via the *Registry Manager*. The Registry Manager is a tool that allows you to see what devices are registered with the IoT Hub, and communicate with those devices by sending cloud to device messages, direct method requests or updating the device twin. You can also use it to register, update or delete IoT devices from the IoT Hub. @@ -439,13 +467,7 @@ To connect to the Registry Manager, you need a connection string. 1. Remove the code from inside the `main` method, but keep the method itself. -1. When multiple messages are received, it only makes sense to process the last one as this is the current soil moisture. It makes no sense to process messages from before. Add the following code to get the last message from the `events` parameter: - - ```python - event = events[-1] - ``` - -1. Below this, add the following code: +1. In the `main` method, add the following code: ```python body = json.loads(event.get_body().decode('utf-8')) diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py index 8d1f586..77eabe1 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py @@ -1,4 +1,3 @@ -from typing import List import logging import azure.functions as func @@ -8,9 +7,7 @@ import os from azure.iot.hub import IoTHubRegistryManager from azure.iot.hub.models import CloudToDeviceMethod -def main(events: List[func.EventHubEvent]): - event = events[-1] - +def main(event: func.EventHubEvent): body = json.loads(event.get_body().decode('utf-8')) device_id = event.iothub_metadata['connection-device-id'] diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json index 0117bdf..cda6466 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json @@ -3,11 +3,11 @@ "bindings": [ { "type": "eventHubTrigger", - "name": "events", + "name": "event", "direction": "in", - "eventHubName": "samples-workitems", + "eventHubName": "", "connection": "IOT_HUB_CONNECTION_STRING", - "cardinality": "many", + "cardinality": "one", "consumerGroup": "$Default", "dataType": "binary" }