0
votes

I'm using the Azure IoT Edge runtime running Linux containers on a Windows host OS. I have two modules, ModuleA and ModuleB. ModuleA have a registered direct method called "MethodA" and ModuleB have a registered direct method called "MethodB".

When I invoke MethodA I want the method to invoke MethodB located in another module (but running in the same IoT Edge runtime).

I'm using the Azure IoT SDK for c# and in the Init() function of ModuleA I have:

await ioTHubModuleClient.SetMethodHandlerAsync("MethodA", MethodA, ioTHubModuleClient);

And in the Init() function of ModuleB I have:

await ioTHubModuleClient.SetMethodHandlerAsync("MethodB", MethodB, null);

The MethodA (that acts like a proxy method):

static async Task<MethodResponse> MethodA(MethodRequest methodRequest, object moduleClient)
    {
        try 
        {
            ModuleClient ioTHubModuleClient = (ModuleClient)moduleClient;             

            // Get deviced id of this device, exposed as a system variable by the iot edge runtime
            var deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");

            // Create the request
            MethodRequest request = new MethodRequest("MethodB", Encoding.UTF8.GetBytes("{ \"Message\": \"Hello\" }"));

            // Execute request
            var resp = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleB", request);

            return resp;               
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
            return await Task.FromResult(new MethodResponse(500));
        } 
    }

The MethodB that I try to invoke from MethodA:

private static Task<MethodResponse> MethodB(MethodRequest methodRequest, object userContext)
    {
        Console.WriteLine("MethodB has been called");

        // Get data but we do not do anything with it
        var data = Encoding.UTF8.GetString(methodRequest.Data);

        Console.WriteLine("Received data: " + data.ToString());

        var methodResponse = new MethodResponse(Encoding.UTF8.GetBytes("{\"status\": \"ok\"}"), 200);
        return Task.FromResult(methodResponse);
    } 

Note that I can invoke these two methods separately just fine from the azure portal or by using the Azure SDK for C# -> ServiceClient class.

My program crashes on the line

var resp = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleB", request);

in MethodA and in the VS Code debugger I get the exception

Exception thrown: 'Microsoft.Azure.Devices.Client.Exceptions.IotHubCommunicationException' in System.Private.CoreLib.dll

And when I check the logs for my Message/StackTrace-print I get:

An error occurred while sending the request. at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.d__172.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Azure.Devices.Client.ModuleClient.d__57.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at OrderGateway.Program.d__3.MoveNext() in /app/Program.cs:line 92

I've tried to initialize the ModuleClient using both Amqp with TCP/WebSocket_Only as well as Mqtt with TCP/WebSocket_Only. I've also tried to run the IoT Edge runtime with these modules on a Ubuntu 18.04 VM without any success.

Even when I try to invoke a method located in the same module with the ioTHubModuleClient.InvokeMethodAsync() I get the same exception / stacktrace...

I also tried to invoke the MethodB from the Init() function of ModuleA to try and not invoke a direct method in the context of a callback but I do get the same exception.

There is an open issue on github that you can find here: https://github.com/Azure/iotedge/issues/204

But it feels that it have kind of stalled and I don't get any help.

From my understanding this should be possible but I don't know what I'm missing?

1

1 Answers

3
votes

I just made this scenario to work, but It's a little bit different than your description.

So, here is what I did: 1-Created 2 Modules, ModuleA and ModuleB; 2-Added your MethodA on ModuleA and your MethodB in Module B; 3-On Module B (Not ModuleA), I added this code:

       while(true)
        {
            try
            {
                Console.WriteLine($"Invoking method On moduleA");
                var deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");
                MethodRequest request = new MethodRequest("MethodA", Encoding.UTF8.GetBytes("{ \"Message\": \"Hello\" }"));

                var response = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleA", request).ConfigureAwait(false);
                Console.WriteLine($"Received response with status {response.Status}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error invoking method {ex}");
            }

            await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false);
        }

That is it. At the begin you will get an exception like this one:

Error invoking method Microsoft.Azure.Devices.Client.Exceptions.DeviceNotFoundException: Device {"message":"Client angelodTransparentGateway/ModuleA not found","exceptionMessage":"","status":0,"payload":null} not registered at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.ExecuteAsync(HttpMethod httpMethod, Uri requestUri, Func3 modifyRequestMessageAsync, Func2 isSuccessful, Func3 processResponseMessageAsync, IDictionary2 errorMappingOverrides, CancellationToken cancellationToken) at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.PostAsync[T1,T2](Uri requestUri, T1 entity, IDictionary2 errorMappingOverrides, IDictionary2 customHeaders, CancellationToken cancellationToken) at Microsoft.Azure.Devices.Client.ModuleClient.InvokeMethodAsync(Uri uri, MethodRequest methodRequest, CancellationToken cancellationToken) at ModuleB.Program.Init() in /app/Program.cs:line 74

But this is by design, and it's just because it takes time for modules to be created after a deployment, but in less than 1 minute I was able to get the succesfull result:

MethodA has been called Received data: {"Message":"Hello"}

If you call MethodB from ModuleA the MethodA won't get called. But just to make sure it worked I also tested module A invoking Module B. Here is the code:

        while(true)
        {
            try
            {
                Console.WriteLine($"Invoking method On moduleB");
                var deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");
                MethodRequest request = new MethodRequest("MethodB", Encoding.UTF8.GetBytes("{ \"Message\": \"Hello\" }"));


                var response = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleB", request).ConfigureAwait(false);
                Console.WriteLine($"Received response with status {response.Status}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error invoking method {ex}");
            }

            await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false);
        }

Both Worked. Don't forget to use .NET version 2.1 (Update that on your dockerfile).