private async Task ReceiveMessageUsingCallbackRecoveryPoolOverAmqpAsync( TestDeviceType type, Client.TransportType transport, int poolSize, int devicesCount, string faultType, string reason, int delayInSec = FaultInjection.DefaultDelayInSec, int durationInSec = FaultInjection.DefaultDurationInSec, ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device, string proxyAddress = null) { // Initialize the service client var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); } async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { var timeout = TimeSpan.FromSeconds(20); using var cts = new CancellationTokenSource(timeout); (Message msg, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(Logger); testDeviceCallbackHandler.ExpectedMessageSentByService = msg; await serviceClient.SendAsync(testDevice.Id, msg).ConfigureAwait(false); Logger.Trace($"{nameof(FaultInjectionPoolAmqpTests)}: Sent message to device {testDevice.Id}: payload='{payload}' p1Value='{p1Value}'"); Client.Message receivedMessage = await deviceClient.ReceiveAsync(timeout).ConfigureAwait(false); await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); receivedMessage.Should().BeNull(); } async Task CleanupOperationAsync(IList <DeviceClient> deviceClients) { await serviceClient.CloseAsync().ConfigureAwait(false); serviceClient.Dispose(); foreach (DeviceClient deviceClient in deviceClients) { deviceClient.Dispose(); } } await FaultInjectionPoolingOverAmqp .TestFaultInjectionPoolAmqpAsync( MessageReceive_DevicePrefix, transport, proxyAddress, poolSize, devicesCount, faultType, reason, delayInSec, durationInSec, InitOperationAsync, TestOperationAsync, CleanupOperationAsync, authScope, Logger) .ConfigureAwait(false); }
// Error injection template method. public static async Task TestErrorInjectionAsync( string devicePrefix, TestDeviceType type, Client.TransportType transport, string proxyAddress, string faultType, string reason, int delayInSec, int durationInSec, Func <DeviceClient, TestDevice, Task> initOperation, Func <DeviceClient, TestDevice, Task> testOperation, Func <Task> cleanupOperation, MsTestLogger logger) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, devicePrefix, type).ConfigureAwait(false); ITransportSettings transportSettings = CreateTransportSettingsFromName(transport, proxyAddress); DeviceClient deviceClient = testDevice.CreateDeviceClient(new ITransportSettings[] { transportSettings }); ConnectionStatus? lastConnectionStatus = null; ConnectionStatusChangeReason?lastConnectionStatusChangeReason = null; int connectionStatusChangeCount = 0; deviceClient.SetConnectionStatusChangesHandler((status, statusChangeReason) => { connectionStatusChangeCount++; lastConnectionStatus = status; lastConnectionStatusChangeReason = statusChangeReason; logger.Trace($"{nameof(FaultInjection)}.{nameof(ConnectionStatusChangesHandler)}: status={status} statusChangeReason={statusChangeReason} count={connectionStatusChangeCount}"); }); var watch = new Stopwatch(); try { await deviceClient.OpenAsync().ConfigureAwait(false); if (transport != Client.TransportType.Http1) { Assert.IsTrue(connectionStatusChangeCount >= 1, $"The expected connection status change should be equal or greater than 1 but was {connectionStatusChangeCount}"); // Normally one connection but in some cases, due to network issues we might have already retried several times to connect. Assert.AreEqual(ConnectionStatus.Connected, lastConnectionStatus, $"The expected connection status should be {ConnectionStatus.Connected} but was {lastConnectionStatus}"); Assert.AreEqual(ConnectionStatusChangeReason.Connection_Ok, lastConnectionStatusChangeReason, $"The expected connection status change reason should be {ConnectionStatusChangeReason.Connection_Ok} but was {lastConnectionStatusChangeReason}"); } await initOperation(deviceClient, testDevice).ConfigureAwait(false); logger.Trace($">>> {nameof(FaultInjection)} Testing baseline"); await testOperation(deviceClient, testDevice).ConfigureAwait(false); int countBeforeFaultInjection = connectionStatusChangeCount; watch.Start(); logger.Trace($">>> {nameof(FaultInjection)} Testing fault handling"); await ActivateFaultInjectionAsync(transport, faultType, reason, delayInSec, durationInSec, deviceClient, logger).ConfigureAwait(false); logger.Trace($"{nameof(FaultInjection)}: Waiting for fault injection to be active: {delayInSec} seconds."); await Task.Delay(TimeSpan.FromSeconds(delayInSec)).ConfigureAwait(false); // For disconnect type faults, the device should disconnect and recover. if (FaultShouldDisconnect(faultType)) { logger.Trace($"{nameof(FaultInjection)}: Confirming fault injection has been actived."); // Check that service issued the fault to the faulting device bool isFaulted = false; for (int i = 0; i < LatencyTimeBufferInSec; i++) { if (connectionStatusChangeCount > countBeforeFaultInjection) { isFaulted = true; break; } await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } Assert.IsTrue(isFaulted, $"The device {testDevice.Id} did not get faulted with fault type: {faultType}"); logger.Trace($"{nameof(FaultInjection)}: Confirmed fault injection has been actived."); // Check the device is back online logger.Trace($"{nameof(FaultInjection)}: Confirming device back online."); for (int i = 0; lastConnectionStatus != ConnectionStatus.Connected && i < durationInSec + LatencyTimeBufferInSec; i++) { await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } Assert.AreEqual(ConnectionStatus.Connected, lastConnectionStatus, $"{testDevice.Id} did not reconnect."); logger.Trace($"{nameof(FaultInjection)}: Confirmed device back online."); // Perform the test operation. logger.Trace($">>> {nameof(FaultInjection)}: Performing test operation for device {testDevice.Id}."); await testOperation(deviceClient, testDevice).ConfigureAwait(false); } else { logger.Trace($"{nameof(FaultInjection)}: Performing test operation while fault injection is being activated."); // Perform the test operation for the faulted device multi times. for (int i = 0; i < LatencyTimeBufferInSec; i++) { logger.Trace($">>> {nameof(FaultInjection)}: Performing test operation for device - Run {i}."); await testOperation(deviceClient, testDevice).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } } await deviceClient.CloseAsync().ConfigureAwait(false); if (transport != Client.TransportType.Http1) { if (FaultInjection.FaultShouldDisconnect(faultType)) { // 4 is the minimum notification count: connect, fault, reconnect, disable. // There are cases where the retry must be timed out (i.e. very likely for MQTT where otherwise // we would attempt to send the fault injection forever.) Assert.IsTrue(connectionStatusChangeCount >= 4, $"The expected connection status change count for {testDevice.Id} should be equal or greater than 4 but was {connectionStatusChangeCount}"); } else { // 2 is the minimum notification count: connect, disable. // We will monitor the test environment real network stability and switch to >=2 if necessary to // account for real network issues. Assert.IsTrue(connectionStatusChangeCount == 2, $"The expected connection status change count for {testDevice.Id} should be 2 but was {connectionStatusChangeCount}"); } Assert.AreEqual(ConnectionStatus.Disabled, lastConnectionStatus, $"The expected connection status should be {ConnectionStatus.Disabled} but was {lastConnectionStatus}"); Assert.AreEqual(ConnectionStatusChangeReason.Client_Close, lastConnectionStatusChangeReason, $"The expected connection status change reason should be {ConnectionStatusChangeReason.Client_Close} but was {lastConnectionStatusChangeReason}"); } } finally { await cleanupOperation().ConfigureAwait(false); logger.Trace($"{nameof(FaultInjection)}: Disposing deviceClient {TestLogger.GetHashCode(deviceClient)}"); deviceClient.Dispose(); watch.Stop(); int timeToFinishFaultInjection = durationInSec * 1000 - (int)watch.ElapsedMilliseconds; if (timeToFinishFaultInjection > 0) { logger.Trace($"{nameof(FaultInjection)}: Waiting {timeToFinishFaultInjection}ms to ensure that FaultInjection duration passed."); await Task.Delay(timeToFinishFaultInjection).ConfigureAwait(false); } } }
public async Task Message_DeviceSendSingleLargeMessageAsync(TestDeviceType testDeviceType, Client.TransportType transportType, int messageSize) { await SendSingleMessage(testDeviceType, transportType, messageSize).ConfigureAwait(false); }
private async Task ReceiveMessageRecoveryPoolOverAmqpAsync( TestDeviceType type, Client.TransportType transport, int poolSize, int devicesCount, string faultType, string reason, int delayInSec = FaultInjection.DefaultDelayInSec, int durationInSec = FaultInjection.DefaultDurationInSec, ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device, string proxyAddress = null) { // Initialize the service client var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { (Message msg, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(Logger); Logger.Trace($"{nameof(FaultInjectionPoolAmqpTests)}: Sending message to device {testDevice.Id}: payload='{payload}' p1Value='{p1Value}'"); await serviceClient.SendAsync(testDevice.Id, msg) .ConfigureAwait(false); Logger.Trace($"{nameof(FaultInjectionPoolAmqpTests)}: Preparing to receive message for device {testDevice.Id}"); await deviceClient.OpenAsync() .ConfigureAwait(false); await MessageReceiveE2ETests.VerifyReceivedC2DMessageAsync(transport, deviceClient, testDevice.Id, msg, payload, Logger) .ConfigureAwait(false); } async Task CleanupOperationAsync(IList <DeviceClient> deviceClients) { await serviceClient.CloseAsync() .ConfigureAwait(false); serviceClient.Dispose(); foreach (DeviceClient deviceClient in deviceClients) { deviceClient.Dispose(); } } await FaultInjectionPoolingOverAmqp .TestFaultInjectionPoolAmqpAsync( MessageReceive_DevicePrefix, transport, proxyAddress, poolSize, devicesCount, faultType, reason, delayInSec, durationInSec, (d, t, h) => { return(Task.FromResult(false)); }, TestOperationAsync, CleanupOperationAsync, authScope, Logger) .ConfigureAwait(false); }
private async Task SendMethodAndRespondRecoveryPoolOverAmqpAsync( TestDeviceType type, Client.TransportType transport, int poolSize, int devicesCount, Func <DeviceClient, string, MsTestLogger, Task <Task> > setDeviceReceiveMethod, string faultType, string reason, int delayInSec = FaultInjection.DefaultDelayInSec, int durationInSec = FaultInjection.DefaultDurationInSec, ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device, string proxyAddress = null) { var testDevicesWithCallbackHandler = new Dictionary <string, TestDeviceCallbackHandler>(); Func <DeviceClient, TestDevice, Task> initOperation = async(deviceClient, testDevice) => { var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, Logger); testDevicesWithCallbackHandler.Add(testDevice.Id, testDeviceCallbackHandler); Logger.Trace($"{nameof(MethodE2EPoolAmqpTests)}: Setting method callback handler for device {testDevice.Id}"); await testDeviceCallbackHandler .SetDeviceReceiveMethodAsync(MethodName, MethodE2ETests.DeviceResponseJson, MethodE2ETests.ServiceRequestJson) .ConfigureAwait(false); }; Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) => { TestDeviceCallbackHandler testDeviceCallbackHandler = testDevicesWithCallbackHandler[testDevice.Id]; using var cts = new CancellationTokenSource(FaultInjection.RecoveryTimeMilliseconds); Logger.Trace($"{nameof(MethodE2EPoolAmqpTests)}: Preparing to receive method for device {testDevice.Id}"); Task serviceSendTask = MethodE2ETests.ServiceSendMethodAndVerifyResponseAsync( testDevice.Id, MethodName, MethodE2ETests.DeviceResponseJson, MethodE2ETests.ServiceRequestJson, Logger); Task methodReceivedTask = testDeviceCallbackHandler.WaitForMethodCallbackAsync(cts.Token); await Task.WhenAll(serviceSendTask, methodReceivedTask).ConfigureAwait(false); }; Func <IList <DeviceClient>, Task> cleanupOperation = async(deviceClients) => { foreach (DeviceClient deviceClient in deviceClients) { deviceClient.Dispose(); } testDevicesWithCallbackHandler.Clear(); await Task.FromResult <bool>(false).ConfigureAwait(false); }; await FaultInjectionPoolingOverAmqp .TestFaultInjectionPoolAmqpAsync( MethodDevicePrefix, transport, proxyAddress, poolSize, devicesCount, faultType, reason, delayInSec, durationInSec, initOperation, testOperation, cleanupOperation, authScope, Logger) .ConfigureAwait(false); }
public TestDevice(string deviceName, TestDeviceType deviceType) { name = deviceName; type = deviceType; }
private async Task SendMessageMuxedOverAmqp( TestDeviceType type, Client.TransportType transport, int poolSize, int devicesCount, ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(transport) { AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings() { MaxPoolSize = unchecked ((uint)poolSize), Pooling = true } } }; ICollection <DeviceClient> deviceClients = new List <DeviceClient>(); Dictionary <DeviceClient, int> deviceClientConnectionStatusChangeCount = new Dictionary <DeviceClient, int>(); try { _log.WriteLine($"{nameof(MessageSendE2EMultiplexingTests)}: Starting the test execution for {devicesCount} devices"); for (int i = 0; i < devicesCount; i++) { ConnectionStatus? lastConnectionStatus = null; ConnectionStatusChangeReason?lastConnectionStatusChangeReason = null; int setConnectionStatusChangesHandlerCount = 0; TestDevice testDevice = await TestDevice.GetTestDeviceAsync($"{DevicePrefix}_{i}_", type).ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings, authScope); deviceClients.Add(deviceClient); deviceClient.SetConnectionStatusChangesHandler((status, statusChangeReason) => { setConnectionStatusChangesHandlerCount++; lastConnectionStatus = status; lastConnectionStatusChangeReason = statusChangeReason; _log.WriteLine($"{nameof(MessageSendE2EMultiplexingTests)}.{nameof(ConnectionStatusChangesHandler)}: status={status} statusChangeReason={statusChangeReason} count={setConnectionStatusChangesHandlerCount}"); deviceClientConnectionStatusChangeCount[deviceClient] = setConnectionStatusChangesHandlerCount; }); _log.WriteLine($"{nameof(MessageSendE2EMultiplexingTests)}: Preparing to send message for device {i}"); await deviceClient.OpenAsync().ConfigureAwait(false); await MessageSend.SendSingleMessageAndVerifyAsync(deviceClient, testDevice.Id).ConfigureAwait(false); } } finally { // Close and dispose all of the device client instances here foreach (DeviceClient deviceClient in deviceClients) { await deviceClient.CloseAsync().ConfigureAwait(false); // The connection status change count should be 2: connect (open) and disabled (close) Assert.IsTrue(deviceClientConnectionStatusChangeCount[deviceClient] == 2); _log.WriteLine($"{nameof(MessageSendE2EMultiplexingTests)}: Disposing deviceClient {TestLogging.GetHashCode(deviceClient)}"); deviceClient.Dispose(); } } }
public static async Task TestErrorInjectionTemplate( string devicePrefix, TestDeviceType type, Client.TransportType transport, string faultType, string reason, int delayInSec, int durationInSec, Func <DeviceClient, TestDevice, Task> initOperation, Func <DeviceClient, TestDevice, Task> testOperation, Func <Task> cleanupOperation) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(devicePrefix, type).ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); ConnectionStatus? lastConnectionStatus = null; ConnectionStatusChangeReason?lastConnectionStatusChangeReason = null; int setConnectionStatusChangesHandlerCount = 0; deviceClient.SetConnectionStatusChangesHandler((status, statusChangeReason) => { s_log.WriteLine($"{nameof(FaultInjection)}.{nameof(ConnectionStatusChangesHandler)}: status={status} statusChangeReason={statusChangeReason} count={setConnectionStatusChangesHandlerCount}"); lastConnectionStatus = status; lastConnectionStatusChangeReason = statusChangeReason; setConnectionStatusChangesHandlerCount++; }); var watch = new Stopwatch(); try { await deviceClient.OpenAsync().ConfigureAwait(false); if (transport != Client.TransportType.Http1) { Assert.AreEqual(1, setConnectionStatusChangesHandlerCount); Assert.AreEqual(ConnectionStatus.Connected, lastConnectionStatus); Assert.AreEqual(ConnectionStatusChangeReason.Connection_Ok, lastConnectionStatusChangeReason); } await initOperation(deviceClient, testDevice).ConfigureAwait(false); s_log.WriteLine($">>> {nameof(FaultInjection)} Testing baseline"); await testOperation(deviceClient, testDevice).ConfigureAwait(false); await ActivateFaultInjection(transport, faultType, reason, delayInSec, durationInSec, deviceClient).ConfigureAwait(false); s_log.WriteLine($">>> {nameof(FaultInjection)} Testing fault handling"); watch.Start(); s_log.WriteLine($"{nameof(FaultInjection)}: Waiting for fault injection to be active: {FaultInjection.WaitForDisconnectMilliseconds}ms"); await Task.Delay(FaultInjection.WaitForDisconnectMilliseconds).ConfigureAwait(false); await testOperation(deviceClient, testDevice).ConfigureAwait(false); await deviceClient.CloseAsync().ConfigureAwait(false); if (transport == Client.TransportType.Mqtt || transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { // Our fault injection is only terminating the connection for MQTT. (HTTP is not connection-oriented, AMQP is not actually terminating the TCP layer.) Assert.IsTrue(setConnectionStatusChangesHandlerCount >= 4); Assert.AreEqual(ConnectionStatus.Disabled, lastConnectionStatus); Assert.AreEqual(ConnectionStatusChangeReason.Client_Close, lastConnectionStatusChangeReason); } } finally { await cleanupOperation().ConfigureAwait(false); await deviceClient.CloseAsync().ConfigureAwait(false); watch.Stop(); int timeToFinishFaultInjection = durationInSec * 1000 - (int)watch.ElapsedMilliseconds; if (timeToFinishFaultInjection > 0) { s_log.WriteLine($"{nameof(FaultInjection)}: Waiting {timeToFinishFaultInjection}ms to ensure that FaultInjection duration passed."); await Task.Delay(timeToFinishFaultInjection).ConfigureAwait(false); } } }
private async Task SendMethodAndRespondRecoveryPoolOverAmqpAsync( TestDeviceType type, Client.TransportType transport, int poolSize, int devicesCount, Func <DeviceClient, string, MsTestLogger, Task <Task> > setDeviceReceiveMethod, string faultType, string reason, TimeSpan delayInSec = default, TimeSpan durationInSec = default, ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device, string proxyAddress = null) { async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { Logger.Trace($"{nameof(MethodE2EPoolAmqpTests)}: Setting method callback handler for device {testDevice.Id}"); await testDeviceCallbackHandler .SetDeviceReceiveMethodAsync(MethodName, MethodE2ETests.DeviceResponseJson, MethodE2ETests.ServiceRequestJson) .ConfigureAwait(false); } async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); Logger.Trace($"{nameof(MethodE2EPoolAmqpTests)}: Preparing to receive method for device {testDevice.Id}"); Task serviceSendTask = MethodE2ETests .ServiceSendMethodAndVerifyResponseAsync( testDevice.Id, MethodName, MethodE2ETests.DeviceResponseJson, MethodE2ETests.ServiceRequestJson, Logger); Task methodReceivedTask = testDeviceCallbackHandler.WaitForMethodCallbackAsync(cts.Token); await Task.WhenAll(serviceSendTask, methodReceivedTask).ConfigureAwait(false); } async Task CleanupOperationAsync(List <DeviceClient> deviceClients, List <TestDeviceCallbackHandler> testDeviceCallbackHandlers) { deviceClients.ForEach(deviceClient => deviceClient.Dispose()); testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); await Task.FromResult <bool>(false).ConfigureAwait(false); } await FaultInjectionPoolingOverAmqp .TestFaultInjectionPoolAmqpAsync( MethodDevicePrefix, transport, proxyAddress, poolSize, devicesCount, faultType, reason, delayInSec == TimeSpan.Zero?FaultInjection.DefaultFaultDelay : delayInSec, durationInSec == TimeSpan.Zero?FaultInjection.DefaultFaultDuration : durationInSec, InitOperationAsync, TestOperationAsync, CleanupOperationAsync, authScope, Logger) .ConfigureAwait(false); }
/// <summary> /// Factory method. /// </summary> /// <param name="namePrefix">The prefix to apply to your device name</param> /// <param name="type">The way the device will authenticate</param> public static async Task <TestDevice> GetTestDeviceAsync(MsTestLogger logger, string namePrefix, TestDeviceType type = TestDeviceType.Sasl) { _logger = logger; string prefix = namePrefix + type + "_"; try { await s_semaphore.WaitAsync().ConfigureAwait(false); TestDevice ret = await CreateDeviceAsync(type, prefix).ConfigureAwait(false); _logger.Trace($"{nameof(GetTestDeviceAsync)}: Using device {ret.Id}."); return(ret); } finally { s_semaphore.Release(); } }
private async Task ReceiveMessageUsingCallbackUpdateHandlerAsync(TestDeviceType type, Client.TransportType transport) { var firstHandlerSemaphore = new SemaphoreSlim(0, 1); var secondHandlerSemaphore = new SemaphoreSlim(0, 1); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // Set the first C2D message handler. await deviceClient.SetReceiveMessageHandlerAsync( async (message, context) => { Logger.Trace($"Received message over the first message handler: MessageId={message.MessageId}"); await deviceClient.CompleteAsync(message).ConfigureAwait(false); firstHandlerSemaphore.Release(); }, deviceClient); // The C2D message should be received over the first callback handler, releasing the corresponding semaphore. using var firstCts = new CancellationTokenSource(s_tenSeconds); (Message firstMessage, _, _) = ComposeC2dTestMessage(Logger); Logger.Trace($"Sending C2D message from service, messageId={firstMessage.MessageId}"); await Task .WhenAll( serviceClient.SendAsync(testDevice.Id, firstMessage), firstHandlerSemaphore.WaitAsync(firstCts.Token)) .ConfigureAwait(false); // Set the second C2D message handler. await deviceClient.SetReceiveMessageHandlerAsync( async (message, context) => { Logger.Trace($"Received message over the second message handler: MessageId={message.MessageId}"); await deviceClient.CompleteAsync(message).ConfigureAwait(false); secondHandlerSemaphore.Release(); }, deviceClient); using var secondCts = new CancellationTokenSource(s_tenSeconds); Func <Task> secondCallbackHandler = async() => { await firstHandlerSemaphore.WaitAsync(secondCts.Token).ConfigureAwait(false); }; // The C2D message should be received over the second callback handler, releasing the corresponding semaphore. // The first callback handler should not be called, meaning its semaphore should not be available to be grabbed. (Message secondMessage, _, _) = ComposeC2dTestMessage(Logger); Logger.Trace($"Sending C2D message from service, messageId={secondMessage.MessageId}"); await Task .WhenAll( serviceClient.SendAsync(testDevice.Id, secondMessage), secondHandlerSemaphore.WaitAsync(secondCts.Token)) .ConfigureAwait(false); secondCallbackHandler.Should().Throw <OperationCanceledException>(); await deviceClient.CloseAsync().ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false); }
private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType type, Client.TransportType transport) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // For Mqtt - we will need to subscribe to the Mqtt receive telemetry topic // before the device can begin receiving c2d messages. if (transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { Client.Message leftoverMessage = await deviceClient.ReceiveAsync(s_fiveSeconds).ConfigureAwait(false); Logger.Trace($"Leftover message on Mqtt was: {leftoverMessage} with Id={leftoverMessage?.MessageId}"); } // First receive message using the polling ReceiveAsync() API. (Message firstMessage, _, _) = ComposeC2dTestMessage(Logger); await serviceClient.SendAsync(testDevice.Id, firstMessage).ConfigureAwait(false); Logger.Trace($"Sent C2D message from service, messageId={firstMessage.MessageId} - to be received on polling ReceiveAsync"); using Client.Message receivedFirstMessage = await deviceClient.ReceiveAsync(s_tenSeconds).ConfigureAwait(false); receivedFirstMessage.MessageId.Should().Be(firstMessage.MessageId); await deviceClient.CompleteAsync(receivedFirstMessage).ConfigureAwait(false); // Now, set a callback on the device client to receive C2D messages. await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); // Now, send a message to the device from the service. (Message secondMessage, _, _) = ComposeC2dTestMessage(Logger); testDeviceCallbackHandler.ExpectedMessageSentByService = secondMessage; await serviceClient.SendAsync(testDevice.Id, secondMessage).ConfigureAwait(false); Logger.Trace($"Sent C2D message from service, messageId={secondMessage.MessageId} - to be received on callback"); // The message should be received on the callback, while a call to ReceiveAsync() should return null. using var cts = new CancellationTokenSource(s_tenSeconds); using Client.Message receivedSecondMessage = await deviceClient.ReceiveAsync(s_tenSeconds).ConfigureAwait(false); await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); receivedSecondMessage.Should().BeNull(); // Now unsubscribe from receiving c2d messages over the callback. await deviceClient.SetReceiveMessageHandlerAsync(null, deviceClient).ConfigureAwait(false); // For Mqtt - since we have explicitly unsubscribed, we will need to resubscribe again // before the device can begin receiving c2d messages. if (transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { Client.Message leftoverMessage = await deviceClient.ReceiveAsync(s_fiveSeconds).ConfigureAwait(false); Logger.Trace($"Leftover message on Mqtt was: {leftoverMessage} with Id={leftoverMessage?.MessageId}"); } // Send a message to the device from the service. (Message thirdMessage, _, _) = ComposeC2dTestMessage(Logger); await serviceClient.SendAsync(testDevice.Id, thirdMessage).ConfigureAwait(false); Logger.Trace($"Sent C2D message from service, messageId={thirdMessage.MessageId} - to be received on polling ReceiveAsync"); // This time, the message should not be received on the callback, rather it should be received on a call to ReceiveAsync(). Func <Task> receiveMessageOverCallback = async() => { await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); }; using Client.Message receivedThirdMessage = await deviceClient.ReceiveAsync(s_tenSeconds).ConfigureAwait(false); receivedThirdMessage.MessageId.Should().Be(thirdMessage.MessageId); receiveMessageOverCallback.Should().Throw <OperationCanceledException>(); await deviceClient.CompleteAsync(receivedThirdMessage).ConfigureAwait(false); firstMessage.Dispose(); secondMessage.Dispose(); thirdMessage.Dispose(); await deviceClient.CloseAsync().ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false); }
private async Task SendMessageRecoveryPoolOverAmqp( TestDeviceType type, Client.TransportType transport, int poolSize, int devicesCount, string faultType, string reason, int delayInSec = FaultInjection.DefaultDelayInSec, int durationInSec = FaultInjection.DefaultDurationInSec, ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { Dictionary <string, EventHubTestListener> eventHubListeners = new Dictionary <string, EventHubTestListener>(); Func <DeviceClient, TestDevice, Task> initOperation = async(deviceClient, testDevice) => { EventHubTestListener testListener = await EventHubTestListener.CreateListener(testDevice.Id).ConfigureAwait(false); eventHubListeners.Add(testDevice.Id, testListener); }; Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) => { _log.WriteLine($"{nameof(FaultInjectionPoolAmqpTests)}: Preparing to send message for device {testDevice.Id}"); await deviceClient.OpenAsync().ConfigureAwait(false); (Client.Message testMessage, string messageId, string payload, string p1Value) = MessageSendE2ETests.ComposeD2CTestMessage(); _log.WriteLine($"{nameof(FaultInjectionPoolAmqpTests)}.{testDevice.Id}: payload='{payload}' p1Value='{p1Value}'"); await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false); EventHubTestListener testListener = eventHubListeners[testDevice.Id]; bool isReceived = await testListener.WaitForMessage(testDevice.Id, payload, p1Value).ConfigureAwait(false); Assert.IsTrue(isReceived, $"Message is not received for device {testDevice.Id}."); }; Func <IList <DeviceClient>, Task> cleanupOperation = async(deviceClients) => { foreach (var listener in eventHubListeners) { await listener.Value.CloseAsync().ConfigureAwait(false); } foreach (DeviceClient deviceClient in deviceClients) { deviceClient.Dispose(); } }; await FaultInjectionPoolingOverAmqp.TestFaultInjectionPoolAmqpAsync( MessageSend_DevicePrefix, transport, poolSize, devicesCount, faultType, reason, delayInSec, durationInSec, initOperation, testOperation, cleanupOperation, authScope).ConfigureAwait(false); }