Azure IoT Edge Integration Test template - Part.1
Published: 2022-08-28 10:31:00

# Summary

This is a part of article series introducing Azure IoT Edge integration template - [azure-iot-edge-integration-test-template](https://github.com/koheikawata/azure-iot-edge-integration-test-template). In this Part.1, I will explain the entire pipeline including Infrastructure as Code and integration test. Part.2 would provide the information about each IoT Edge module, and Part.3 share about the IoT Edge manifest generator.

- [Azure IoT Edge Integration Test template - Part.2](https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l)
- [Azure IoT Edge Integration Test template - Part.3](https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part3-5eld)

# TOC
- [Overview](#overview)
- [Architecture](#architecture)
- [Test steps](#steps)
- [Infrastructure deployment](#infra)
  - [Network Security Group - inbound Port 22](#nsg)
  - [VM domain name label](#dns)
  - [IoT Hub consumer group](#cg)
- [Setup and installation](#setup)
  - [VM Service Connection](#vmsvc)
  - [Blob Contributor Role](#role)
- [Code deployment and test execution](#deployment)
  - [edge-module.yml](#edge-module)
  - [test-prep.yml](#test-prep)
  - [test-execution.yml](#test-execution)
  - [test-cleanup.yml](#test-cleanup)

# Overview <a name="overview"></a>

When you have multiple Azure IoT Edge modules on an edge device and want to update codes of one of those modules, you can make sure the code quality by implementing linter and unit tests, but it is difficult to validate communications among modules. That is why executing integration tests every time you update the software.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ee70envc6jzk8o8g1v4a.png)

 In this template, you can execute integration tests of Azure IoT Edge modules on Test Environment on Azure Virtual Machine. The test procedure is all automated by Azure Pipelines. By taking advantage of this template, you deploy and execute integration tests before deploying your code to the edge device.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7eutd23kim7ysm7mzjf.png)

# Architecture <a name="architecture"></a>

This template includes six IoT Edge sample modules - [FileGenerator](https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/FileGenerator), [FileUpdater](https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/FileUpdater), [FileUploader](https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/FileUploader), [IothubConnector](https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/IothubConnector), [WeatherObserver](https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/WeatherObserver), and [LocalBlobStorage](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-store-data-blob). Details of IoT Edge modules are explained in [Azure IoT Edge Integration Test template - Part.2](https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l). 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpgl4y6bkggnab9808ue.png)

# Test steps <a name="steps"></a>

You need to execute three steps, **1) Infrastructure deployment**, **2) Setup and installation**, and **3) Code deployment and test execution**. Everything to know to run this template is described in [Getting-started](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/docs/Getting-started.md), but I am going instruct details and important points for those pipelines. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15gr4qrzjpbj76limxtm.png)

# Infrastructure deployment <a name="infra"></a>

Azure resources needed for this template are defined in [main.bicep](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep) and you can run the IaC(Infrastructure as Code) pipeline of [iac.yml](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/iac.yml) on Azure Pipelines.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcve1i48n2n5dbzvxel7.png)

## Network Security Group - inbound Port 22 <a name="nsg"></a>

[main.bicep](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep) defines a Network Security Group inbound rule that opens Port 22. This is necessary for you to access the VM through SSH when installing Azure IoT Edge in the next step and for Azure Pipelines agent to set up VM's directory and grant directory access permissions in [test-prep.yml](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-prep.yml).

```
properties: {
  securityRules: [
    {
      name: 'SSH'
      properties: {
        protocol: 'Tcp'
        sourcePortRange: '*'
        destinationPortRange: '22'
        sourceAddressPrefix: '*'
        destinationAddressPrefix: '*'
        access: 'Allow'
        priority: 100
        direction: 'Inbound'
      }
    }
  ]
}
```

## VM domain name label <a name="dns"></a>

Setting up the domain name label for the VM is important because IP Address is dynamically allocated and Azure Pipelines agent accesses with VM Service Connection descrited in [Create SSH Service Connection](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/docs/Getting-started.md#create-ssh-service-connection).

Host name should be `edge-{BASE_NAME}.{LOCATION}.cloudapp.azure.com`, which is defined in [main.bicep](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep).

```
var dns_label = 'edge-${base_name}'

resource PublicIp 'Microsoft.Network/publicIPAddresses@2021-05-01' = {
  name: public_ip_name
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    publicIPAddressVersion: 'IPv4'
    dnsSettings: {
      domainNameLabel: dns_label
    }
    idleTimeoutInMinutes: 4
  }
}
```

## IoT Hub consumer group <a name="cg"></a>

It is better to create [consumer groups](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-features#consumer-groups) of IoT Hub. If you want to consume messages on the IoT Hub through another tool such as [Azure IoT Hub Explorer](https://docs.microsoft.com/en-us/azure/iot-fundamentals/howto-use-iot-explorer) that would cause errors of the integration test. [main.bicep](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep) deploys the consumer group that dedicates to Azure Pipelines agent executing integration tests.

```
param iothub_cg_name string
resource IoTHubConsumerGroup 'Microsoft.Devices/IotHubs/eventHubEndpoints/ConsumerGroups@2021-07-02' = {
  name: '${IoTHub.name}/events/${iothub_cg_name}'
  properties:{
    name: iothub_cg_name
  }
}
```

# Setup and installation <a name="setup"></a>
## VM Service Connection <a name="vmsvc"></a>

In this template, it uses [SSH service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#ssh-service-connection). It is possible you use bash commands like `ssh testuser@edge-{BASE_NAME}.{LOCATION}.cloudapp.azure.com`. By combining `retryCountOnTaskFailure` of Azure Pipelines task, you can handle unstable connection errors of SSH.

## Blob Contributor Role <a name="role"></a>

You need to set up manually `Storage Blob Data Contributor` of [Azure built-in roles](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) of Azure RBAC, which is tied to your Azure Subscription so Azure Pipelines agent can generate SAS token of Blob Storage. You need Azure Subscription Owner or User Access Administrator role.

```
az role assignment create `
    --role "Storage Blob Data Contributor" `
    --assignee {Object ID of Azure Service Connection} `
    --scope "/subscriptions/{Azure Subscription ID}/resourceGroups/rg-{BASE_NAME}/providers/Microsoft.Storage/storageAccounts/st{BASE_NAME}"
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rljjg2cspql0v29adylg.png)

# Code deployment and test execution <a name="deployment"></a>

## [edge-module.yml](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/edge-module.yml)  <a name="edge-module"></a>

- Call this template with each IoT Edge module. This builds and pushes container images to Azure Container Registry.

- Use not Azure Pipeline [Docker task](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/docker) but docker commands because it does not need to create [Docker Registry service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#docker-registry-service-connection) manually. The process that Azure Pipelines extracts Azure Container Registry key, buil, and push is all automated.

```
acrkey=$(az acr credential show --name $(ACR_NAME) --query passwords[0].value -o tsv)
cd ${{ parameters.dockerfileDirectory }}
docker login -u $(ACR_NAME) -p $acrkey $(ACR_NAME).azurecr.io
docker build --rm -f Dockerfile -t $(ACR_NAME).azurecr.io/${{ parameters.repositoryName }}:$(Build.BuildNumber) .
docker push $(ACR_NAME).azurecr.io/${{ parameters.repositoryName }}:$(Build.BuildNumber)
```

## [test-prep.yml](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-prep.yml) <a name="test-prep"></a>

- Call this template with IoT Edge modules as parameters. The parameters are used for iterative tasks that check if module images exist in Azure Container Registry and each module is running on IoT Edge runtime.

```
- template: ./templates/test-prep.yml
  parameters:
    azureSvcName: $(AZURE_SVC_NAME)
    vmSshSvcName: $(VM_SVC_NAME)
    EdgeImages:
      module1:
        name: IothubConnector
        repository: iothub-connector
        tag: $(Build.BuildNumber)
      module2:
        name: WeatherObserver
        repository: weather-observer
        tag: $(Build.BuildNumber)
      module3:
        name: FileGenerator
        repository: file-generator
        tag: $(Build.BuildNumber)
      module4:
        name: FileUploader
        repository: file-uploader
        tag: $(Build.BuildNumber)
      module5:
        name: FileUpdater
        repository: file-updater
        tag: $(Build.BuildNumber)
```

- SSH task
  - Remove `/edge` directory to refresh the leftover of past test executions.
  - Grant read, write, and execute permissions to the host machine directory. Azure IoT Edge Hub with `UID 1000` and Azure IoT Edge local blob storage with `user ID 11000` and `user group ID 11000`.
    - [Host system permissions](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-access-host-storage-from-module?view=iotedge-2020-11#host-system-permissions)
    - [Granting directory access to container user on Linux](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-store-data-blob?view=iotedge-2020-11#granting-directory-access-to-container-user-on-linux)
  - Install the tree command so it can show the host machine directory in the log.
  - Restart Azure IoT Edge runtime because you deleted the directory that the current modules implement bind mount. To refresh the connection between modules and directory, you need to restart the runtime, otherwise it would cause errors.

```
if [ -d "/edge" ]
then
  sudo rm -r /edge
fi

sudo mkdir -p $(FILE_UPLOADER_DIR)
sudo chown -R 1000 $(FILE_UPLOADER_DIR)
sudo chmod -R 700 $(FILE_UPLOADER_DIR)

sudo mkdir -p $(FILE_UPDATER_DIR)
sudo chown -R 1000 $(FILE_UPDATER_DIR)
sudo chmod -R 700 $(FILE_UPDATER_DIR)

sudo mkdir -p $(LOCAL_BLOB_STORAGE_DIR)
sudo chown -R 11000:11000 $(LOCAL_BLOB_STORAGE_DIR)
sudo chmod -R 700 $(LOCAL_BLOB_STORAGE_DIR)

sudo apt-get install tree
tree /edge

sudo iotedge system restart
```

# [test-execution.yml](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-execution.yml)  <a name="test-execution"></a>
- Use acynchronous bash method `&` so the Azure Pipelines agent sends a direct method request to IoT Hub and at the same time listens to messages on IoT Hub sent from IoT Edge modules. `--timeout` is currently set `30` sec. Sometimes the response from IoT Edge is slow and they do not respond and cause errors. 30 seconds is probably is good time to wait. If it is longer than 30 seconds, something is going wrong on Edge modules. 

```
az iot hub invoke-module-method --hub-name $(IOTHUB_NAME) --device-id $(IOTHUB_DEVICE_ID) --module-id IothubConnector --method-name request_weather_report --method-payload '{"city": "Tokyo"}' &
testResult=$(az iot hub monitor-events --hub-name $(IOTHUB_NAME) --device-id $(IOTHUB_DEVICE_ID) --module-id IothubConnector --cg $(IOTHUB_CONSUMER_GROUP) --timeout 30 -y)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmnom35bh0avniq1up62.png)

# [test-cleanup.yml](https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-cleanup.yml)   <a name="test-cleanup"></a>
- Remove all directory but keep the blob container `weather`

```
az storage blob directory delete --account-name $(STORAGE_ACCOUNT_NAME) --container-name $(BLOB_CONTAINER_NAME) --directory-path $(TEST_ORGANIZATION_NAME) --recursive
```