I have one deployment manifest, which I can upload using this command:
az iot edge deployment create --hub-name IoTHubName --content ./deployment.arm64v8.json --deployment-id
"V20241119_arm64v8_v2" --target-condition "tags.DeviceDeploymentVersion='V20241119-arm64v8'" --priority 28
Works great and is successful every time..
Now for the API via the SDK..
var registryManager = MsAzureDevicesNS.RegistryManager.CreateFromConnectionString(_IOTHubDevicesConfiguration.IOTHubConnectionString);
var configuration = new Configuration(edgeDeploymentModel.DeploymentId)
{
Content = new ConfigurationContent
{
ModulesContent = JsonSerializer.Deserialize<IDictionary<string, IDictionary<string, object>>>(
edgeDeploymentModel.ConfigurationContent, _jsonSerializerOptions)
},
TargetCondition = edgeDeploymentModel.TargetCondition,
Priority = edgeDeploymentModel.Priority,
ETag = string.Empty,
};
edgeDeploymentModel.ConfigurationContent is the json read from the exact same file -> deployment.arm64v8.json.
I then:
await registryManager.AddConfigurationAsync(configuration);
Error message is pretty generic (thanks JSON)
This is the deployment manifest that was used to upload (correctly) using the cli
{
"modulesContent": {
"$edgeAgent": {
"properties.desired": {
"schemaVersion": "1.1",
"runtime": {
"type": "docker",
"settings": {
"minDockerVersion": "v1.25",
"loggingOptions": "",
"registryCredentials": {
"gfsedgemodules": {
"username": "XXXEdgeModules",
"password": "",
"address": "XXXedgemodules.azurecr.io"
}
}
}
},
"systemModules": {
"edgeAgent": {
"env": {
"StorageFolder": {
"value": "/tmp/edgeAgent"
}
},
"type": "docker",
"settings": {
"image": "mcr.microsoft/azureiotedge-agent:1.5",
"createOptions": "{\"HostConfig\":{\"privileged\":true,\"Binds_Comment\":\"When binding, define the host path to the container path\",\"Binds\":[\"/srv/iotEdge/edgeAgent:/tmp/edgeAgent\"]}}"
}
},
"edgeHub": {
"env": {
"StorageFolder": {
"value": "/tmp/edgeHub"
},
"UpstreamProtocol": {
"value": "Mqtt"
}
},
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
"image": "mcr.microsoft/azureiotedge-hub:1.5",
"createOptions": "{\"HostConfig\":{\"Binds\":[\"/srv/iotEdge/edgeHub:/tmp/edgeHub:\"],\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
}
}
},
"modules": {
"XXXConnectIoTModule": {
"version": "2.0.0",
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
"image": "XXXedgemodules.azurecr.io/XXXconnectrepo:v20241120.v1-arm64v8",
"createOptions": "{\"HostConfig\":{\"NetworkMode\":\"host\",\"privileged\":true,\"Binds\":[\"/dev/i2c-1:/dev/i2c-1\"],\"Mount_Comment\":\"Source is the host, and Target is within the Pod - the Source points to Target\",\"Mounts\":[{\"Commment\":\"Read the device initialization file\",\"Source\":\"/home/XXXAdmin001/XXXInitialization\",\"Target\":\"/host/XXXInitialization\",\"Type\":\"bind\",\"ReadOnly\":true},{\"Commment\":\"CPU Temperature of device\",\"Source\":\"/sys/class/thermal/thermal_zone0\",\"Target\":\"/host/sys/class/thermal/thermal_zone0\",\"Type\":\"bind\",\"ReadOn",
"createOptions01": "ly\":true},{\"Commment\":\"Get Serial Number of the device\",\"Source\":\"/sys/firmware/devicetree/base/\",\"Target\":\"/host/sys/firmware/devicetree/base/\",\"Type\":\"bind\",\"ReadOnly\":true},{\"Commment\":\"Get the host name of the device\",\"Source\":\"/proc/sys/kernel/\",\"Target\":\"/host/proc/sys/kernel/\",\"Type\":\"bind\",\"ReadOnly\":true},{\"Commment\":\"Get the CPU Info of the device\",\"Source\":\"/proc/\",\"Target\":\"/host/proc/\",\"Type\":\"bind\",\"ReadOnly\":true}]},\"NetworkingConfig\":{\"EndpointsConfig\":{\"host\":{}}}}"
},
"env": {
"Version": {
"value": "v20241120.V1"
},
"RuntimeLogLevel": {
"value": "Information"
}
}
}
}
}
},
"$edgeHub": {
"properties.desired": {
"schemaVersion": "1.2",
"routes": {
"XXXConnectIoTModuleToIoTHub": {
"route": "FROM /messages/modules/XXXModule/outputs/* INTO $upstream"
}
},
"storeAndForwardConfiguration": {
"timeToLiveSecs": 7200
}
}
},
"XXXModule": {
"properties.desired.version": "v20241120.V1"
}
}
}
System.ArgumentException
HResult=0x80070057
Message={"Message":"ErrorCode:InvalidConfigurationContent;The content provided for configuration is invalid. Please check and try again","ExceptionMessage":"Tracking ID:ee7f302c1982487d942975af604e7af3-G:0-TimeStamp:11/20/2024 00:09:17"}
Source=Microsoft.Azure.Devices
StackTrace:
at Microsoft.Azure.Devices.HttpClientHelper.<ExecuteAsync>d__36.MoveNext()
at Microsoft.Azure.Devices.HttpClientHelper.<ExecuteAsync>d__32.MoveNext()
at Microsoft.Azure.Devices.HttpClientHelper.<PutAsync>d__11`1.MoveNext()
at GFSConnectApp.Engines.IoTHubDevicesEngine.<UploadConfigurationToIotHubAsync>d__11.MoveNext() in C:\Users\Richard\source\repos\PhxBiz\GFSConnectSLN\GFSConnectApp\Engines\IoTHubDevicesEngine.cs:line 158
This exception was originally thrown at this call stack:
Microsoft.Azure.Devices.HttpClientHelper.ExecuteAsync(System.Net.Http.HttpClient, System.Net.Http.HttpMethod, System.Uri, System.Func<System.Net.Http.HttpRequestMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Func<System.Net.Http.HttpResponseMessage, bool>, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Collections.Generic.IDictionary<System.Net.HttpStatusCode, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.Tasks.Task<System.Exception>>>, System.Threading.CancellationToken)
Microsoft.Azure.Devices.HttpClientHelper.ExecuteAsync(System.Net.Http.HttpMethod, System.Uri, System.Func<System.Net.Http.HttpRequestMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Collections.Generic.IDictionary<System.Net.HttpStatusCode, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.Tasks.Task<System.Exception>>>, System.Threading.CancellationToken)
GFSConnectApp.Engines.IoTHubDevicesEngine.UploadConfigurationToIotHubAsync(GFSConnectApp.Model.EdgeConfigurationModel) in IoTHubDevicesEngine.cs
Same file, I was thinking the SDK would allow it.... perhaps the deserialization into the dictionary is where an error is occurring
I have one deployment manifest, which I can upload using this command:
az iot edge deployment create --hub-name IoTHubName --content ./deployment.arm64v8.json --deployment-id
"V20241119_arm64v8_v2" --target-condition "tags.DeviceDeploymentVersion='V20241119-arm64v8'" --priority 28
Works great and is successful every time..
Now for the API via the SDK..
var registryManager = MsAzureDevicesNS.RegistryManager.CreateFromConnectionString(_IOTHubDevicesConfiguration.IOTHubConnectionString);
var configuration = new Configuration(edgeDeploymentModel.DeploymentId)
{
Content = new ConfigurationContent
{
ModulesContent = JsonSerializer.Deserialize<IDictionary<string, IDictionary<string, object>>>(
edgeDeploymentModel.ConfigurationContent, _jsonSerializerOptions)
},
TargetCondition = edgeDeploymentModel.TargetCondition,
Priority = edgeDeploymentModel.Priority,
ETag = string.Empty,
};
edgeDeploymentModel.ConfigurationContent is the json read from the exact same file -> deployment.arm64v8.json.
I then:
await registryManager.AddConfigurationAsync(configuration);
Error message is pretty generic (thanks JSON)
This is the deployment manifest that was used to upload (correctly) using the cli
{
"modulesContent": {
"$edgeAgent": {
"properties.desired": {
"schemaVersion": "1.1",
"runtime": {
"type": "docker",
"settings": {
"minDockerVersion": "v1.25",
"loggingOptions": "",
"registryCredentials": {
"gfsedgemodules": {
"username": "XXXEdgeModules",
"password": "",
"address": "XXXedgemodules.azurecr.io"
}
}
}
},
"systemModules": {
"edgeAgent": {
"env": {
"StorageFolder": {
"value": "/tmp/edgeAgent"
}
},
"type": "docker",
"settings": {
"image": "mcr.microsoft/azureiotedge-agent:1.5",
"createOptions": "{\"HostConfig\":{\"privileged\":true,\"Binds_Comment\":\"When binding, define the host path to the container path\",\"Binds\":[\"/srv/iotEdge/edgeAgent:/tmp/edgeAgent\"]}}"
}
},
"edgeHub": {
"env": {
"StorageFolder": {
"value": "/tmp/edgeHub"
},
"UpstreamProtocol": {
"value": "Mqtt"
}
},
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
"image": "mcr.microsoft/azureiotedge-hub:1.5",
"createOptions": "{\"HostConfig\":{\"Binds\":[\"/srv/iotEdge/edgeHub:/tmp/edgeHub:\"],\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
}
}
},
"modules": {
"XXXConnectIoTModule": {
"version": "2.0.0",
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
"image": "XXXedgemodules.azurecr.io/XXXconnectrepo:v20241120.v1-arm64v8",
"createOptions": "{\"HostConfig\":{\"NetworkMode\":\"host\",\"privileged\":true,\"Binds\":[\"/dev/i2c-1:/dev/i2c-1\"],\"Mount_Comment\":\"Source is the host, and Target is within the Pod - the Source points to Target\",\"Mounts\":[{\"Commment\":\"Read the device initialization file\",\"Source\":\"/home/XXXAdmin001/XXXInitialization\",\"Target\":\"/host/XXXInitialization\",\"Type\":\"bind\",\"ReadOnly\":true},{\"Commment\":\"CPU Temperature of device\",\"Source\":\"/sys/class/thermal/thermal_zone0\",\"Target\":\"/host/sys/class/thermal/thermal_zone0\",\"Type\":\"bind\",\"ReadOn",
"createOptions01": "ly\":true},{\"Commment\":\"Get Serial Number of the device\",\"Source\":\"/sys/firmware/devicetree/base/\",\"Target\":\"/host/sys/firmware/devicetree/base/\",\"Type\":\"bind\",\"ReadOnly\":true},{\"Commment\":\"Get the host name of the device\",\"Source\":\"/proc/sys/kernel/\",\"Target\":\"/host/proc/sys/kernel/\",\"Type\":\"bind\",\"ReadOnly\":true},{\"Commment\":\"Get the CPU Info of the device\",\"Source\":\"/proc/\",\"Target\":\"/host/proc/\",\"Type\":\"bind\",\"ReadOnly\":true}]},\"NetworkingConfig\":{\"EndpointsConfig\":{\"host\":{}}}}"
},
"env": {
"Version": {
"value": "v20241120.V1"
},
"RuntimeLogLevel": {
"value": "Information"
}
}
}
}
}
},
"$edgeHub": {
"properties.desired": {
"schemaVersion": "1.2",
"routes": {
"XXXConnectIoTModuleToIoTHub": {
"route": "FROM /messages/modules/XXXModule/outputs/* INTO $upstream"
}
},
"storeAndForwardConfiguration": {
"timeToLiveSecs": 7200
}
}
},
"XXXModule": {
"properties.desired.version": "v20241120.V1"
}
}
}
System.ArgumentException
HResult=0x80070057
Message={"Message":"ErrorCode:InvalidConfigurationContent;The content provided for configuration is invalid. Please check and try again","ExceptionMessage":"Tracking ID:ee7f302c1982487d942975af604e7af3-G:0-TimeStamp:11/20/2024 00:09:17"}
Source=Microsoft.Azure.Devices
StackTrace:
at Microsoft.Azure.Devices.HttpClientHelper.<ExecuteAsync>d__36.MoveNext()
at Microsoft.Azure.Devices.HttpClientHelper.<ExecuteAsync>d__32.MoveNext()
at Microsoft.Azure.Devices.HttpClientHelper.<PutAsync>d__11`1.MoveNext()
at GFSConnectApp.Engines.IoTHubDevicesEngine.<UploadConfigurationToIotHubAsync>d__11.MoveNext() in C:\Users\Richard\source\repos\PhxBiz\GFSConnectSLN\GFSConnectApp\Engines\IoTHubDevicesEngine.cs:line 158
This exception was originally thrown at this call stack:
Microsoft.Azure.Devices.HttpClientHelper.ExecuteAsync(System.Net.Http.HttpClient, System.Net.Http.HttpMethod, System.Uri, System.Func<System.Net.Http.HttpRequestMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Func<System.Net.Http.HttpResponseMessage, bool>, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Collections.Generic.IDictionary<System.Net.HttpStatusCode, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.Tasks.Task<System.Exception>>>, System.Threading.CancellationToken)
Microsoft.Azure.Devices.HttpClientHelper.ExecuteAsync(System.Net.Http.HttpMethod, System.Uri, System.Func<System.Net.Http.HttpRequestMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.CancellationToken, System.Threading.Tasks.Task>, System.Collections.Generic.IDictionary<System.Net.HttpStatusCode, System.Func<System.Net.Http.HttpResponseMessage, System.Threading.Tasks.Task<System.Exception>>>, System.Threading.CancellationToken)
GFSConnectApp.Engines.IoTHubDevicesEngine.UploadConfigurationToIotHubAsync(GFSConnectApp.Model.EdgeConfigurationModel) in IoTHubDevicesEngine.cs
Same file, I was thinking the SDK would allow it.... perhaps the deserialization into the dictionary is where an error is occurring
The error is related to the structure of the ModulesContent
dictionary
and configuration properties.
I referred to this MSDOC for importing and exporting device identities in Azure IoT Hub.
Additionally, I referred to the following GitHub repositories for ConfigurationContent and ImportExportDevicesSample
Below code is to create a configuration that will be uploaded to IoT Hub using the RegistryManager
.
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Devices;
namespace AzureIoT
{
public class IoTHubDeviceManagement
{
private readonly string _iotHubConnectionString;
public IoTHubDeviceManagement(string connectionString)
{
_iotHubConnectionString = connectionString;
}
public async Task UploadConfigurationAsync(string deploymentId, string configurationContentJson, string targetCondition, int priority)
{
try
{
var registryManager = RegistryManager.CreateFromConnectionString(_iotHubConnectionString);
var configData = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(configurationContentJson);
var modulesContentJson = configData["modulesContent"].ToString();
var labelsJson = configData.ContainsKey("labels") ? configData["labels"].ToString() : "{}";
var metricsJson = configData.ContainsKey("metrics") ? configData["metrics"].ToString() : "{}";
var modulesContent = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(modulesContentJson);
var modulesContentAsIDictionary = new Dictionary<string, IDictionary<string, object>>();
foreach (var module in modulesContent)
{
var moduleContent = JsonSerializer.Deserialize<Dictionary<string, object>>(module.Value.ToString());
modulesContentAsIDictionary.Add(module.Key, moduleContent);
}
var configuration = new Microsoft.Azure.Devices.Configuration(deploymentId)
{
Content = new Microsoft.Azure.Devices.ConfigurationContent
{
ModulesContent = modulesContentAsIDictionary
},
TargetCondition = targetCondition,
Priority = priority,
ETag = string.Empty
};
if (!string.IsNullOrEmpty(labelsJson))
{
var labels = JsonSerializer.Deserialize<Dictionary<string, string>>(labelsJson);
configuration.Labels = labels;
}
if (!string.IsNullOrEmpty(metricsJson))
{
var metrics = JsonSerializer.Deserialize<Dictionary<string, object>>(metricsJson);
}
await registryManager.AddConfigurationAsync(configuration);
Console.WriteLine("Configuration uploaded successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error uploading configuration: {ex.Message}");
}
}
}
class Program
{
static async Task Main(string[] args)
{
string connectionString = "HostName=AzureIOTHubName.azure-devices;SharedAccessKeyName=iothubowner;SharedAccessKey=kkk..";
string configurationContentJson = @"
{
""modulesContent"": {
""$edgeAgent"": {
""properties.desired"": {
""modules"": {
""module1"": {
""type"": ""docker"",
""settings"": {
""image"": ""mydockerimage:v1"",
""createOptions"": ""\""""
}
}
}
}
},
""$edgeHub"": {
""properties.desired"": {
""routes"": {
""myRoute"": ""FROM /messages/* INTO $upstream""
}
}
}
},
""labels"": {
""environment"": ""production"",
""location"": ""building_1""
},
""metrics"": {
""queries"": {
""statusQuery"": ""SELECT deviceId FROM devices WHERE properties.reported.status = 'OK'""
}
},
""priority"": 10
}";
string targetCondition = "tags.DeviceDeploymentVersion='V20241119-arm64v8' AND tags.environment='production' AND tags.location='building_1'";
var iotDeviceManagement = new IoTHubDeviceManagement(connectionString);
await iotDeviceManagement.UploadConfigurationAsync("raviteja", configurationContentJson, targetCondition, 28);
}
}
}