Search code examples
c#azureazure-iot-hubazure-iot-edgeazure-iot-hub-device-management

Azure IoT Edge stop a module programmatically


I'm trying to change the "status" (running or stopped) and the "restartPolicy" of an IoT Edge module programmatically in order to stop a module without having to recreate the whole deployment for the Device.

I've seen that the Edge Agent's Twin has the modules' deployment information in his desiredProperties and I've tried to patch that by using the following code (which uses the Microsoft.Azure.Devices NuGet packages)

   public async Task ShutdownModule(string deviceId, string moduleId)
    {
        var twinEdgeAgent = await _registryManager.GetTwinAsync(deviceId, "$edgeAgent");

        var patchJson = $"{{\"properties\":{{\"desired\":{{\"modules\":{{\"{moduleId}\":{{\"status\": \"stopped\", \"restartPolicy\": \"never\"}}}}}}}}}}";
        await _registryManager.UpdateTwinAsync(deviceId, "$edgeAgent", patchJson, twinEdgeAgent.ETag);
    } 

Unfortunately this doesn't work and I'm getting an UnauthorizedException with the message "ErrorCode:SystemModuleModifyUnauthorizedAccess;Unauthorized to modify reserved module.". It looks like that I can't change the desired properties of the Edge Agent module.

Is there a way to change this property without having to recreate the whole deployment JSON, or at least is there a way to get this deployment JSON so that I can modify the properties I need to change?


Solution

  • I managed to do it through the deployment API by first reconstructing the existing deployment through the EdgeAgent, EdgeHub and all modules twins.

    Here's a resume of the method I ended up writing:

    var modulesContent = new Dictionary<string, IDictionary<string, object>>();
    
    var twinEdgeAgent = await _registryManager.GetTwinAsync(deviceId, "$edgeAgent");
    var agentModules = twinEdgeAgent.Properties.Desired[ModulesJsonPropertyName];
    
    agentModules[myModuleId]["status"] = "stopped";
    agentModules[myModuleId]["restartPolicy"] = "never";
    
    var desiredProperties = twinEdgeAgent.GetDesiredPropertiesDictionary();
    
    modulesContent.Add("$edgeAgent", edgeHubDesiredProperties);
    
    var twinEdgeHub = await _registryManager.GetTwinAsync(deviceId, "$edgeHub");
    var edgeHubDesiredProperties = twinEdgeHub.GetDesiredPropertiesDictionary();
    modulesContent.Add("$edgeHub", edgeHubDesiredProperties);
    
    // foreach modules contained in agentModules also add 
    // the module's twin desired properties in the dictionary (not shown for brevity)
    
    await _registryManager.ApplyConfigurationContentOnDeviceAsync(
                deviceId,
                new ConfigurationContent { ModulesContent = modulesContent });
    
    internal static class TwinExtensions
    {
        private const string DesiredPropertiesAttribute = "properties.desired";
    
        public static IDictionary<string, object> GetDesiredPropertiesDictionary(this Twin twin)
        {
            if (twin == null)
            {
                throw new ArgumentNullException(nameof(twin));
            }
    
            var twinDesiredProperties = twin.Properties.Desired;
            twinDesiredProperties.ClearMetadata();
    
            var twinDesiredPropertiesDictionary =
                JsonConvert.DeserializeObject<Dictionary<string, object>>(twinDesiredProperties.ToJson());
    
            return new Dictionary<string, object> {{DesiredPropertiesAttribute, twinDesiredPropertiesDictionary}};
        }
    }
    

    Maybe there's a better / simpler solution but we are using a similar approach to automate the upgrade of the module's runtime image and a few other things, so I was able to regroup all those changes in the same code.

    It would be greatly simplified if there was a way to get the deployment JSON directly but I didn't find any.