Azure/azure-iot-sdk-csharp

Not able to restart the custom edge module while using the inbuilt "RestartModule" direct method of $edgeAgent from custom iot edge module.

malavvakharia opened this issue · 14 comments

Expected Behavior:

We can restart the custom module by invoking the $edgeAgent "RestartModule" direct method.But as of now it will be giving the error.

image

Steps to Reproduce:

Provide a detailed set of steps to reproduce the bug.

  1. I am using the .NET sdk for invoking the inbuilt direct method of $edgeAgent.(Note: Calling the inbuilt direct method from the custom module not from $edgeAgent itself.)

  2. Code snippet (C#):

var DataAsJson = @"{
""schemaVersion"": ""1.0"",
""id"": ""edgemodule1""
}";
var methodRequest = new MethodRequest("RestartModule", Encoding.UTF8.GetBytes(DataAsJson), TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));

         var result = await _moduleClient.InvokeMethodAsync("DeviceId","$edgeAgent", methodRequest);
  1. After deploying the module when we invoking the direct method calls it will give the error as shown in image.

my apologies @malavvakharia

I'm able to reproduce the issue when invoking direct methods on $edgeAgent, however I'm able to invoke methods in other modules, so I suspect this might a limitation for $edgeAgent.

Let me review what's the expected behavior of $egeAgent and I'll update this ticket.

Thanks,
Rido

#region Assembly Microsoft.Azure.Devices.Client, Version=1.42.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
    _cancellationToken = cancellationToken;
    MqttTransportSettings mqttSetting = new(TransportType.Mqtt_Tcp_Only);
    ITransportSettings[] settings = { mqttSetting };

    // Open a connection to the Edge runtime
    _moduleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);

    // Reconnect is not implented because we'll let docker restart the process when the connection is lost
    _moduleClient.SetConnectionStatusChangesHandler((status, reason) =>
        _logger.LogWarning("Connection changed: Status: {status} Reason: {reason}", status, reason));

    await _moduleClient.OpenAsync(cancellationToken);

    _logger.LogInformation("IoT Hub module client initialized.");

    var iotEdgeDeviceId = Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");

    var echoResp = await _moduleClient.InvokeMethodAsync(iotEdgeDeviceId, 
        "module-sample", new MethodRequest("echo", Encoding.UTF8.GetBytes("\"hello\"")));

    _logger.LogInformation("Response from module-sample {r} ", echoResp.ResultAsJson);

    MethodRequest request = new(
        "RestartModule", 
        Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new
        {
            schemaVersion = "1.0",
            id = "SimulatedTemperatureSensor"
        })),
        TimeSpan.FromSeconds(15),
        TimeSpan.FromSeconds(5));

    string moduleId = "%24edgeAgent";
    
    _logger.LogInformation("Sending request to {d}-{m} method {n} with {p}", iotEdgeDeviceId, moduleId, request.Name, request.DataAsJson);

    var response = await _moduleClient.InvokeMethodAsync(iotEdgeDeviceId!, moduleId, request, cancellationToken);

    _logger.LogInformation("Response status: {status}, payload: {payload}", response.Status, response.ResultAsJson);
}

fails with

<4>EdgeAgentClient.ModuleBackgroundService[0] Connection changed: Status: Connected Reason: Connection_Ok
<6>EdgeAgentClient.ModuleBackgroundService[0] IoT Hub module client initialized.
<6>EdgeAgentClient.ModuleBackgroundService[0] Response from module-sample "hellohello" 
<6>EdgeAgentClient.ModuleBackgroundService[0] Sending request to tr-gateway-%24edgeAgent method RestartModule with {"schemaVersion":"1.0","id":"SimulatedTemperatureSensor"}
<3>Microsoft.Extensions.Hosting.Internal.Host[9] BackgroundService failed Microsoft.Azure.Devices.Client.Exceptions.DeviceNotFoundException: Device {"message":"Client tr-gateway/$edgeAgent not found"} not registered    at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.ExecuteAsync(HttpMethod httpMethod, Uri requestUri, Func`3 modifyRequestMessageAsync, Func`2 isSuccessful, Func`3 processResponseMessageAsync, IDictionary`2 errorMappingOverrides, CancellationToken cancellationToken)    at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.PostAsync[T1,T2](Uri requestUri, T1 entity, IDictionary`2 errorMappingOverrides, IDictionary`2 customHeaders, CancellationToken cancellationToken)    at Microsoft.Azure.Devices.Client.Transport.HttpTransportHandler.InvokeMethodAsync(MethodInvokeRequest methodInvokeRequest, Uri uri, CancellationToken cancellationToken)    at Microsoft.Azure.Devices.Client.ModuleClient.InvokeMethodAsync(Uri uri, MethodRequest methodRequest, CancellationToken cancellationToken)    at EdgeAgentClient.ModuleBackgroundService.ExecuteAsync(CancellationToken cancellationToken) in C:\code\temp\EdgeAgentClient\ModuleBackgroundService.cs:line 55    at Microsoft.Extensions.Hosting.Internal.Host.TryExecuteBackgroundServiceAsync(BackgroundService backgroundService)
<2>Microsoft.Extensions.Hosting.Internal.Host[10] The HostOptions.BackgroundServiceExceptionBehavior is configured to StopHost. A BackgroundService has thrown an unhandled exception, and the IHost instance is stopping. To avoid this behavior, configure this to Ignore; however the BackgroundService will not be restarted. Microsoft.Azure.Devices.Client.Exceptions.DeviceNotFoundException: Device {"message":"Client tr-gateway/$edgeAgent not found"} not registered    at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.ExecuteAsync(HttpMethod httpMethod, Uri requestUri, Func`3 modifyRequestMessageAsync, Func`2 isSuccessful, Func`3 processResponseMessageAsync, IDictionary`2 errorMappingOverrides, CancellationToken cancellationToken)    at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.PostAsync[T1,T2](Uri requestUri, T1 entity, IDictionary`2 errorMappingOverrides, IDictionary`2 customHeaders, CancellationToken cancellationToken)    at Microsoft.Azure.Devices.Client.Transport.HttpTransportHandler.InvokeMethodAsync(MethodInvokeRequest methodInvokeRequest, Uri uri, CancellationToken cancellationToken)    at Microsoft.Azure.Devices.Client.ModuleClient.InvokeMethodAsync(Uri uri, MethodRequest methodRequest, CancellationToken cancellationToken)    at EdgeAgentClient.ModuleBackgroundService.ExecuteAsync(CancellationToken cancellationToken) in C:\code\temp\EdgeAgentClient\ModuleBackgroundService.cs:line 55    at Microsoft.Extensions.Hosting.Internal.Host.TryExecuteBackgroundServiceAsync(BackgroundService backgroundService)

This sample invokes a method on a device, but should be straight forward to update to use the overload that accepts the moduleId

await InvokeMethodAsync(parameters.DeviceId, serviceClient);

This sample invokes a method on a device, but should be straight forward to update to use the overload that accepts the moduleId

await InvokeMethodAsync(parameters.DeviceId, serviceClient);

@rido-min,

That I have already tried just quick question here we have to pass which connection string here? (Custom module, $edgeAgentModule or Hub)

Reason I have already tried with the custom module connection string and as mentioned earlier It will give the authorization error.

If we have to use $edgeAgent connection string, then how to automatically fetch it in our custom module?

using var serviceClient = ServiceClient.CreateFromConnectionString(parameters.HubConnectionString);

This sample invokes a method on a device, but should be straight forward to update to use the overload that accepts the moduleId

await InvokeMethodAsync(parameters.DeviceId, serviceClient);

@rido-min,

That I have already tried just quick question here we have to pass which connection string here? (Custom module, $edgeAgentModule or Hub)

Reason I have already tried with the custom module connection string and as mentioned earlier It will give the authorization error.

If we have to use $edgeAgent connection string, then how to automatically fetch it in our custom module?

using var serviceClient = ServiceClient.CreateFromConnectionString(parameters.HubConnectionString);

image

@rido-min, Got the success but have just one doubt how to get the iot hub connection string from custom module environment variable? Do you have any idea?

For example, we get the device-Id like below:

string deviceId = Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");

the iothub connection string is not available for devices, you will need (not recommended) to include it in your module deployment.

the iothub connection string is not available for devices, you will need (not recommended) to include it in your module deployment.

@rido-min ,
Then that is not best practice how to set the connection string ?

you can include the connection string in the configuration of your module, or as environment variable, but again this is strongly discouraged.

I'd like to understand your use case.. Why do you need to restart a module from another module?

@malavvakharia
this is a not supported feature, is there anything else we can do to help? otherwise please consider closing this issue

@rido-min ,
I can close ticket but as you mentioned "you can include the connection string in the configuration of your module, or as environment variable, but again this is strongly discouraged."(#3373 (comment)) so how to handle this situation? As you say there is not any inbuilt environment variable to fetch the device connection string then how to do that?

Use case: Need to restart the custom module when any twin change detection found.

[please dont do this] you can add any string to the environment variables section in the deployManifest.json.

I would try to avoid restarting the module, the SDK APIs should let you to observe any twin changes and process those without restarting

@rido-min ,

Agreed what you are saying regarding the restart module, in my use case I am using the broker details from twin and connect to it as well as use it's disconnect event to retry the connection now If I try to disconnect to that broker as twin changes it will try to retry the connection as per logic so In that case I have to restart the module or handle it on other way so it will not try to retry the connection again if that broker details removed from the twin.