Azure / azure-iot-sdk-csharp

A C# SDK for connecting devices to Microsoft Azure IoT services
Other
463 stars 493 forks source link

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

Open malavvakharia opened 11 months ago

malavvakharia commented 11 months ago

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);
  3. After deploying the module when we invoking the direct method calls it will give the error as shown in image.

rido-min commented 11 months ago

Hi @malavvakharia

To invoke a method on a module you should use the ModuleClient, from the picture seems you are using the DeviceClient.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.devices.client.moduleclient.invokemethodasync?view=azure-dotnet#microsoft-azure-devices-client-moduleclient-invokemethodasync(system-string-system-string-microsoft-azure-devices-client-methodrequest-system-threading-cancellationtoken)

malavvakharia commented 11 months ago

Hi @malavvakharia

To invoke a method on a module you should use the ModuleClient, from the picture seems you are using the DeviceClient.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.devices.client.moduleclient.invokemethodasync?view=azure-dotnet#microsoft-azure-devices-client-moduleclient-invokemethodasync(system-string-system-string-microsoft-azure-devices-client-methodrequest-system-threading-cancellationtoken)

Hello @rido-min ,

I am already using the module client. Maybe that's not the issue.

image

rido-min commented 11 months ago

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)
rido-min commented 11 months ago

reported in https://github.com/Azure/iotedge/issues/7116

rido-min commented 11 months ago

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

https://github.com/Azure/azure-iot-sdk-csharp/blob/a84c614e98fd2259a965e7385b92c6d96043de57/iothub/service/samples/getting%20started/InvokeDeviceMethod/Program.cs#L35

malavvakharia commented 11 months ago

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

https://github.com/Azure/azure-iot-sdk-csharp/blob/a84c614e98fd2259a965e7385b92c6d96043de57/iothub/service/samples/getting%20started/InvokeDeviceMethod/Program.cs#L35

@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?

https://github.com/Azure/azure-iot-sdk-csharp/blob/a84c614e98fd2259a965e7385b92c6d96043de57/iothub/service/samples/getting%20started/InvokeDeviceMethod/Program.cs#L33

malavvakharia commented 11 months ago

This sample invokes a method on a device, but should be straight forward to update to use the overload that accepts the moduleId https://github.com/Azure/azure-iot-sdk-csharp/blob/a84c614e98fd2259a965e7385b92c6d96043de57/iothub/service/samples/getting%20started/InvokeDeviceMethod/Program.cs#L35

@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?

https://github.com/Azure/azure-iot-sdk-csharp/blob/a84c614e98fd2259a965e7385b92c6d96043de57/iothub/service/samples/getting%20started/InvokeDeviceMethod/Program.cs#L33

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");

rido-min commented 11 months ago

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

malavvakharia commented 11 months ago

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 ?

rido-min commented 11 months ago

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?

rido-min commented 10 months ago

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

malavvakharia commented 10 months ago

@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."(https://github.com/Azure/azure-iot-sdk-csharp/issues/3373#issuecomment-1739767131) 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.

rido-min commented 10 months ago

[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

malavvakharia commented 10 months ago

@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.