public static async Task ServiceSendMethodAndVerifyNotReceivedAsync( string deviceId, string methodName, MsTestLogger logger, TimeSpan responseTimeout = default, ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); try { CloudToDeviceMethodResult response = await serviceClient .InvokeDeviceMethodAsync( deviceId, new CloudToDeviceMethod(methodName, methodTimeout).SetPayloadJson(null)) .ConfigureAwait(false); } catch (DeviceNotFoundException) { } finally { await serviceClient.CloseAsync().ConfigureAwait(false); serviceClient.Dispose(); } }
public static async Task ServiceSendMethodAndVerifyResponseAsync( string deviceId, string moduleId, string methodName, string respJson, string reqJson, MsTestLogger logger, TimeSpan responseTimeout = default, ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = await serviceClient.InvokeDeviceMethodAsync( deviceId, moduleId, new CloudToDeviceMethod(methodName, responseTimeout).SetPayloadJson(reqJson)).ConfigureAwait(false); logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Method status: {response.Status}."); Assert.AreEqual(200, response.Status, $"The expected response status should be 200 but was {response.Status}"); string payload = response.GetPayloadAsJson(); Assert.AreEqual(respJson, payload, $"The expected response payload should be {respJson} but was {payload}"); await serviceClient.CloseAsync().ConfigureAwait(false); serviceClient.Dispose(); }
public AmqpConnectionStatusChange(MsTestLogger logger) { LastConnectionStatus = null; LastConnectionStatusChangeReason = null; ConnectionStatusChangesHandlerCount = 0; _logger = logger; }
public AmqpConnectionStatusChange(string deviceId, MsTestLogger logger) { LastConnectionStatus = null; LastConnectionStatusChangeReason = null; ConnectionStatusChangeCount = 0; _deviceId = deviceId; _logger = logger; }
internal static void GenerateIntermediateCertificateSignedCertificateFiles( string leafCertificateSubject, string intermediateCertificateSubject, DirectoryInfo destinationCertificateFolder, MsTestLogger logger) { GenerateSignedCertificateFiles(leafCertificateSubject, intermediateCertificateSubject, destinationCertificateFolder, logger); }
public static async Task <EnrollmentGroup> CreateEnrollmentGroupAsync( ProvisioningServiceClient provisioningServiceClient, AttestationMechanismType attestationType, string groupId, ReprovisionPolicy reprovisionPolicy, AllocationPolicy allocationPolicy, CustomAllocationDefinition customAllocationDefinition, ICollection <string> iothubs, DeviceCapabilities capabilities, MsTestLogger logger) { Attestation attestation; switch (attestationType) { case AttestationMechanismType.Tpm: throw new NotSupportedException("Group enrollments do not support tpm attestation"); case AttestationMechanismType.SymmetricKey: string primaryKey = CryptoKeyGenerator.GenerateKey(32); string secondaryKey = CryptoKeyGenerator.GenerateKey(32); attestation = new SymmetricKeyAttestation(primaryKey, secondaryKey); break; case AttestationMechanismType.X509: default: throw new NotSupportedException("Test code has not been written for testing this attestation type yet"); } var enrollmentGroup = new EnrollmentGroup(groupId, attestation) { Capabilities = capabilities, ReprovisionPolicy = reprovisionPolicy, AllocationPolicy = allocationPolicy, CustomAllocationDefinition = customAllocationDefinition, IotHubs = iothubs, }; EnrollmentGroup createdEnrollmentGroup = null; await RetryOperationHelper .RetryOperationsAsync( async() => { createdEnrollmentGroup = await provisioningServiceClient.CreateOrUpdateEnrollmentGroupAsync(enrollmentGroup).ConfigureAwait(false); }, s_provisioningServiceRetryPolicy, s_retryableExceptions, logger) .ConfigureAwait(false); if (createdEnrollmentGroup == null) { throw new ArgumentException($"The enrollment entry with group Id {groupId} could not be created, exiting test."); } return(createdEnrollmentGroup); }
/// <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) { s_logger = logger; string prefix = namePrefix + type + "_"; try { await s_semaphore.WaitAsync().ConfigureAwait(false); TestDevice ret = await CreateDeviceAsync(type, prefix).ConfigureAwait(false); s_logger.Trace($"{nameof(GetTestDeviceAsync)}: Using device {ret.Id}."); return(ret); } finally { s_semaphore.Release(); } }
public static async Task DeleteCreatedEnrollmentAsync( EnrollmentType?enrollmentType, string registrationId, string groupId, MsTestLogger logger) { using ProvisioningServiceClient dpsClient = CreateProvisioningService(); try { if (enrollmentType == EnrollmentType.Individual) { await RetryOperationHelper .RetryOperationsAsync( async() => { await dpsClient.DeleteIndividualEnrollmentAsync(registrationId).ConfigureAwait(false); }, s_provisioningServiceRetryPolicy, s_retryableExceptions, logger) .ConfigureAwait(false); } else if (enrollmentType == EnrollmentType.Group) { await RetryOperationHelper .RetryOperationsAsync( async() => { await dpsClient.DeleteEnrollmentGroupAsync(groupId).ConfigureAwait(false); }, s_provisioningServiceRetryPolicy, s_retryableExceptions, logger) .ConfigureAwait(false); } } catch (Exception ex) { Console.WriteLine($"Cleanup of enrollment failed due to {ex}."); } }
/// <summary> /// Rety an async operation based on the retry strategy supplied. /// </summary> /// <param name="asyncOperation">The async operation to be retried.</param> /// <param name="retryPolicy">The retry policy to be applied.</param> /// <param name="retryableExceptions">The exceptions to be retried on.</param> /// <param name="logger">The <see cref="MsTestLogger"/> instance to be used.</param> /// <returns></returns> public static async Task RetryOperationsAsync(Func <Task> asyncOperation, IRetryPolicy retryPolicy, HashSet <Type> retryableExceptions, MsTestLogger logger) { int counter = 0; bool shouldRetry; do { TimeSpan retryInterval; try { await asyncOperation().ConfigureAwait(false); break; } catch (Exception ex) when(retryableExceptions.Any(e => e.IsInstanceOfType(ex))) { shouldRetry = retryPolicy.ShouldRetry(++counter, ex, out retryInterval); logger.Trace($"Attempt {counter}: operation did not succeed: {ex}"); if (!shouldRetry) { logger.Trace($"Encountered an exception that will not be retried - attempt: {counter}; exception: {ex}"); throw; } } logger.Trace($"Will retry operation in {retryInterval}."); await Task.Delay(retryInterval).ConfigureAwait(false); }while (shouldRetry); }
public static async Task Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(DeviceClient deviceClient, object propValue, MsTestLogger logger) { var propName = Guid.NewGuid().ToString(); logger.Trace($"{nameof(Twin_DeviceSetsReportedPropertyAndGetsItBackAsync)}: name={propName}, value={propValue}"); var props = new TwinCollection(); props[propName] = propValue; await deviceClient.UpdateReportedPropertiesAsync(props).ConfigureAwait(false); Twin deviceTwin = await deviceClient.GetTwinAsync().ConfigureAwait(false); var actual = deviceTwin.Properties.Reported[propName]; Assert.AreEqual(JsonConvert.SerializeObject(actual), JsonConvert.SerializeObject(propValue)); }
public static async Task <Task> SetTwinPropertyUpdateCallbackHandlerAsync(DeviceClient deviceClient, string expectedPropName, object expectedPropValue, MsTestLogger logger) { var propertyUpdateReceived = new TaskCompletionSource <bool>(); string userContext = "myContext"; await deviceClient .SetDesiredPropertyUpdateCallbackAsync( (patch, context) => { logger.Trace($"{nameof(SetTwinPropertyUpdateCallbackHandlerAsync)}: DesiredProperty: {patch}, {context}"); try { Assert.AreEqual(JsonConvert.SerializeObject(expectedPropValue), JsonConvert.SerializeObject(patch[expectedPropName])); Assert.AreEqual(userContext, context, "Context"); } catch (Exception e) { propertyUpdateReceived.SetException(e); } finally { propertyUpdateReceived.SetResult(true); } return(Task.FromResult <bool>(true)); }, userContext) .ConfigureAwait(false); return(propertyUpdateReceived.Task); }
public static async Task TestFaultInjectionPoolAmqpAsync( string devicePrefix, Client.TransportType transport, string proxyAddress, int poolSize, int devicesCount, string faultType, string reason, int delayInSec, int durationInSec, Func <DeviceClient, TestDevice, TestDeviceCallbackHandler, Task> initOperation, Func <DeviceClient, TestDevice, TestDeviceCallbackHandler, Task> testOperation, Func <IList <DeviceClient>, Task> cleanupOperation, ConnectionStringAuthScope authScope, MsTestLogger logger) { var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(transport) { AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings() { MaxPoolSize = unchecked ((uint)poolSize), Pooling = true, }, Proxy = proxyAddress == null ? null : new WebProxy(proxyAddress), } }; IList <TestDevice> testDevices = new List <TestDevice>(); IList <DeviceClient> deviceClients = new List <DeviceClient>(); IList <TestDeviceCallbackHandler> testDeviceCallbackHandlers = new List <TestDeviceCallbackHandler>(); IList <AmqpConnectionStatusChange> amqpConnectionStatuses = new List <AmqpConnectionStatusChange>(); IList <Task> operations = new List <Task>(); // Arrange // Initialize the test device client instances // Set the device client connection status change handler logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)} Initializing Device Clients for multiplexing test."); for (int i = 0; i < devicesCount; i++) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, $"{devicePrefix}_{i}_").ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings, authScope); var amqpConnectionStatusChange = new AmqpConnectionStatusChange(testDevice.Id, logger); deviceClient.SetConnectionStatusChangesHandler(amqpConnectionStatusChange.ConnectionStatusChangesHandler); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, logger); testDevices.Add(testDevice); deviceClients.Add(deviceClient); testDeviceCallbackHandlers.Add(testDeviceCallbackHandler); amqpConnectionStatuses.Add(amqpConnectionStatusChange); operations.Add(initOperation(deviceClient, testDevice, testDeviceCallbackHandler)); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); var watch = new Stopwatch(); try { // Act-Assert // Perform the test operation and verify the operation is successful // Perform baseline test operation for (int i = 0; i < devicesCount; i++) { logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)}: Performing baseline operation for device {i}."); operations.Add(testOperation(deviceClients[i], testDevices[i], testDeviceCallbackHandlers[i])); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); int countBeforeFaultInjection = amqpConnectionStatuses[0].ConnectionStatusChangeCount; // Inject the fault into device 0 watch.Start(); logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: {testDevices[0].Id} Requesting fault injection type={faultType} reason={reason}, delay={delayInSec}s, duration={durationInSec}s"); var faultInjectionMessage = FaultInjection.ComposeErrorInjectionProperties(faultType, reason, delayInSec, durationInSec); await deviceClients[0].SendEventAsync(faultInjectionMessage).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 faulted device should disconnect and all devices should recover. if (FaultInjection.FaultShouldDisconnect(faultType)) { logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirming fault injection has been actived."); // Check that service issued the fault to the faulting device [device 0] bool isFaulted = false; for (int i = 0; i < FaultInjection.LatencyTimeBufferInSec; i++) { if (amqpConnectionStatuses[0].ConnectionStatusChangeCount > countBeforeFaultInjection) { isFaulted = true; break; } await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } Assert.IsTrue(isFaulted, $"The device {testDevices[0].Id} did not get faulted with fault type: {faultType}"); logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirmed fault injection has been actived."); // Check all devices are back online logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirming all devices back online."); bool notRecovered = true; int j = 0; for (int i = 0; notRecovered && i < durationInSec + FaultInjection.LatencyTimeBufferInSec; i++) { notRecovered = false; for (j = 0; j < devicesCount; j++) { if (amqpConnectionStatuses[j].LastConnectionStatus != ConnectionStatus.Connected) { await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); notRecovered = true; break; } } } if (notRecovered) { Assert.Fail($"{testDevices[j].Id} did not reconnect."); } logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirmed all devices back online."); // Perform the test operation for all devices for (int i = 0; i < devicesCount; i++) { logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)}: Performing test operation for device {i}."); operations.Add(testOperation(deviceClients[i], testDevices[i], testDeviceCallbackHandlers[i])); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); } else { logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Performing test operation while fault injection is being activated."); // Perform the test operation for the faulted device multi times. for (int i = 0; i < FaultInjection.LatencyTimeBufferInSec; i++) { logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)}: Performing test operation for device 0 - Run {i}."); await testOperation(deviceClients[0], testDevices[0], testDeviceCallbackHandlers[0]).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } } // Close the device client instances for (int i = 0; i < devicesCount; i++) { operations.Add(deviceClients[i].CloseAsync()); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); // Verify the connection status change checks. // For all of the devices - last connection status should be "Disabled", with reason "Client_close" for (int i = 0; i < devicesCount; i++) { // For the faulted device [device 0] - verify the connection status change count. if (i == 0) { if (FaultInjection.FaultShouldDisconnect(faultType)) { // 4 is the minimum notification count: connect, fault, reconnect, disable. Assert.IsTrue(amqpConnectionStatuses[i].ConnectionStatusChangeCount >= 4, $"The expected connection status change count for {testDevices[i].Id} should equals or greater than 4 but was {amqpConnectionStatuses[i].ConnectionStatusChangeCount}"); } else { // 2 is the minimum notification count: connect, disable. Assert.IsTrue(amqpConnectionStatuses[i].ConnectionStatusChangeCount >= 2, $"The expected connection status change count for {testDevices[i].Id} should be 2 but was {amqpConnectionStatuses[i].ConnectionStatusChangeCount}"); } } Assert.AreEqual(ConnectionStatus.Disabled, amqpConnectionStatuses[i].LastConnectionStatus, $"The expected connection status should be {ConnectionStatus.Disabled} but was {amqpConnectionStatuses[i].LastConnectionStatus}"); Assert.AreEqual(ConnectionStatusChangeReason.Client_Close, amqpConnectionStatuses[i].LastConnectionStatusChangeReason, $"The expected connection status change reason should be {ConnectionStatusChangeReason.Client_Close} but was {amqpConnectionStatuses[i].LastConnectionStatusChangeReason}"); } } finally { // Close the service-side components and dispose the device client instances. await cleanupOperation(deviceClients).ConfigureAwait(false); 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); } } }
private static async Task CompleteMessageMixOrder(TestDeviceType type, Client.TransportType transport, MsTestLogger logger) { using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, s_devicePrefix, type).ConfigureAwait(false); using (DeviceClient deviceClient = testDevice.CreateDeviceClient(transport)) using (var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await deviceClient.OpenAsync().ConfigureAwait(false); if (transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { // Dummy ReceiveAsync to ensure mqtt subscription registration before SendAsync() is called on service client. await deviceClient.ReceiveAsync(TIMESPAN_FIVE_SECONDS).ConfigureAwait(false); } await serviceClient.OpenAsync().ConfigureAwait(false); var messages = new List <Client.Message>(); for (int i = 0; i < MESSAGE_COUNT; i++) { (Message msg, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(logger); await serviceClient.SendAsync(testDevice.Id, msg).ConfigureAwait(false); Client.Message message = await deviceClient.ReceiveAsync(TIMESPAN_ONE_MINUTE).ConfigureAwait(false); if (message == null) { Assert.Fail("No message received."); } messages.Add(message); } for (int i = 0; i < MESSAGE_COUNT; i++) { var stopwatch = new Stopwatch(); stopwatch.Start(); await deviceClient.CompleteAsync(messages[MESSAGE_COUNT - 1 - i]).ConfigureAwait(false); stopwatch.Stop(); Assert.IsFalse(stopwatch.ElapsedMilliseconds > deviceClient.OperationTimeoutInMilliseconds, $"CompleteAsync is over {deviceClient.OperationTimeoutInMilliseconds}"); } await deviceClient.CloseAsync().ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false); } }
// Fault timings: // -------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --- device in normal operation --- | FaultRequested | --- <delayInSec> --- | --- Device in fault mode for <durationInSec> --- | --- device in normal operation --- // -------------------------------------------------------------------------------------------------------------------------------------------------------------------- public static async Task ActivateFaultInjectionAsync(Client.TransportType transport, string faultType, string reason, int delayInSec, int durationInSec, DeviceClient deviceClient, MsTestLogger logger) { logger.Trace($"{nameof(ActivateFaultInjectionAsync)}: Requesting fault injection type={faultType} reason={reason}, delay={delayInSec}s, duration={FaultInjection.DefaultDurationInSec}s"); uint oldTimeout = deviceClient.OperationTimeoutInMilliseconds; try { // For MQTT FaultInjection will terminate the connection prior to a PUBACK // which leads to an infinite loop trying to resend the FaultInjection message. if (transport == Client.TransportType.Mqtt || transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { deviceClient.OperationTimeoutInMilliseconds = (uint)delayInSec * 1000; } await deviceClient .SendEventAsync( ComposeErrorInjectionProperties( faultType, reason, delayInSec, durationInSec)) .ConfigureAwait(false); } catch (IotHubCommunicationException ex) { logger.Trace($"{nameof(ActivateFaultInjectionAsync)}: {ex}"); // For quota injection, the fault is only seen for the original HTTP request. if (transport == Client.TransportType.Http1) { throw; } } catch (TimeoutException ex) { logger.Trace($"{nameof(ActivateFaultInjectionAsync)}: {ex}"); // For quota injection, the fault is only seen for the original HTTP request. if (transport == Client.TransportType.Http1) { throw; } } finally { deviceClient.OperationTimeoutInMilliseconds = oldTimeout; logger.Trace($"{nameof(ActivateFaultInjectionAsync)}: Fault injection requested."); } }
public static async Task <Task> SetModuleReceiveMethodDefaultHandlerAsync(ModuleClient moduleClient, string methodName, MsTestLogger logger) { var methodCallReceived = new TaskCompletionSource <bool>(); await moduleClient.SetMethodDefaultHandlerAsync( (request, context) => { logger.Trace($"{nameof(SetDeviceReceiveMethodDefaultHandlerAsync)}: ModuleClient method: {request.Name} {request.ResponseTimeout}."); try { Assert.AreEqual(methodName, request.Name, $"The expected method name should be {methodName} but was {request.Name}"); Assert.AreEqual(ServiceRequestJson, request.DataAsJson, $"The expected respose payload should be {ServiceRequestJson} but was {request.DataAsJson}"); methodCallReceived.SetResult(true); } catch (Exception ex) { methodCallReceived.SetException(ex); } return(Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(DeviceResponseJson), 200))); }, null).ConfigureAwait(false); return(methodCallReceived.Task); }
private static async Task <Task> SetTwinPropertyUpdateCallbackObsoleteHandlerAsync(DeviceClient deviceClient, string expectedPropName, object expectedPropValue, MsTestLogger logger) { #pragma warning disable CS0618 string userContext = "myContext"; var propertyUpdateReceived = new TaskCompletionSource <bool>(); await deviceClient .SetDesiredPropertyUpdateCallback( (patch, context) => { logger.Trace($"{nameof(SetTwinPropertyUpdateCallbackHandlerAsync)}: DesiredProperty: {patch}, {context}"); try { Assert.AreEqual(expectedPropValue, patch[expectedPropName].ToString()); Assert.AreEqual(userContext, context, "Context"); } catch (Exception e) { propertyUpdateReceived.SetException(e); } finally { propertyUpdateReceived.SetResult(true); } return(Task.FromResult <bool>(true)); }, userContext) .ConfigureAwait(false); #pragma warning restore CS0618 return(propertyUpdateReceived.Task); }
private static async Task ReceiveMessageWithoutTimeoutCheckAsync(DeviceClient dc, TimeSpan bufferTime, MsTestLogger logger) { var sw = new Stopwatch(); while (true) { try { logger.Trace($"{nameof(ReceiveMessageWithoutTimeoutCheckAsync)} - Calling ReceiveAsync()"); sw.Restart(); using Client.Message message = await dc.ReceiveAsync().ConfigureAwait(false); sw.Stop(); logger.Trace($"{nameof(ReceiveMessageWithoutTimeoutCheckAsync)} - Received message={message}; time taken={sw.ElapsedMilliseconds} ms"); if (message == null) { break; } await dc.CompleteAsync(message).ConfigureAwait(false); } finally { TimeSpan maxLatency = TimeSpan.FromMilliseconds(dc.OperationTimeoutInMilliseconds) + bufferTime; if (sw.Elapsed > maxLatency) { Assert.Fail($"ReceiveAsync did not return in {maxLatency}, instead it took {sw.Elapsed}."); } } } }
public static async Task VerifyReceivedC2dMessageWithCancellationTokenAsync(Client.TransportType transport, DeviceClient dc, string deviceId, string payload, string p1Value, MsTestLogger logger) { var sw = new Stopwatch(); bool received = false; sw.Start(); while (!received && sw.ElapsedMilliseconds < FaultInjection.RecoveryTimeMilliseconds) { logger.Trace($"Receiving messages for device {deviceId}."); using var cts = new CancellationTokenSource(s_oneMinute); using Client.Message receivedMessage = await dc.ReceiveAsync(cts.Token).ConfigureAwait(false); if (receivedMessage == null) { Assert.Fail($"No message is received for device {deviceId} in {s_oneMinute}."); } try { // always complete message await dc.CompleteAsync(receivedMessage).ConfigureAwait(false); } catch (Exception) { // ignore exception from CompleteAsync } string messageData = Encoding.ASCII.GetString(receivedMessage.GetBytes()); logger.Trace($"{nameof(VerifyReceivedC2dMessageWithCancellationTokenAsync)}: Received message: for {deviceId}: {messageData}"); if (Equals(payload, messageData)) { Assert.AreEqual(1, receivedMessage.Properties.Count, $"The count of received properties did not match for device {deviceId}"); System.Collections.Generic.KeyValuePair <string, string> prop = receivedMessage.Properties.Single(); Assert.AreEqual("property1", prop.Key, $"The key \"property1\" did not match for device {deviceId}"); Assert.AreEqual(p1Value, prop.Value, $"The value of \"property1\" did not match for device {deviceId}"); received = true; } } sw.Stop(); Assert.IsTrue(received, $"No message received for device {deviceId} with payload={payload} in {FaultInjection.RecoveryTimeMilliseconds}."); }
public static async Task VerifyReceivedC2DMessageAsync(Client.TransportType transport, DeviceClient dc, string deviceId, Message message, string payload, MsTestLogger logger) { string receivedMessageDestination = $"/devices/{deviceId}/messages/deviceBound"; var sw = new Stopwatch(); bool received = false; sw.Start(); while (!received && sw.ElapsedMilliseconds < FaultInjection.RecoveryTimeMilliseconds) { Client.Message receivedMessage = null; try { logger.Trace($"Receiving messages for device {deviceId}."); if (transport == Client.TransportType.Http1) { // timeout on HTTP is not supported receivedMessage = await dc.ReceiveAsync().ConfigureAwait(false); } else { receivedMessage = await dc.ReceiveAsync(s_oneMinute).ConfigureAwait(false); } if (receivedMessage == null) { Assert.Fail($"No message is received for device {deviceId} in {s_oneMinute}."); } try { // always complete message await dc.CompleteAsync(receivedMessage).ConfigureAwait(false); } catch (Exception) { // ignore exception from CompleteAsync } Assert.AreEqual(receivedMessage.MessageId, message.MessageId, "Recieved message Id is not what was sent by service"); Assert.AreEqual(receivedMessage.UserId, message.UserId, "Recieved user Id is not what was sent by service"); Assert.AreEqual(receivedMessage.To, receivedMessageDestination, "Recieved message destination is not what was sent by service"); string messageData = Encoding.ASCII.GetString(receivedMessage.GetBytes()); logger.Trace($"{nameof(VerifyReceivedC2DMessageAsync)}: Received message: for {deviceId}: {messageData}"); if (Equals(payload, messageData)) { Assert.AreEqual(1, receivedMessage.Properties.Count, $"The count of received properties did not match for device {deviceId}"); System.Collections.Generic.KeyValuePair <string, string> prop = receivedMessage.Properties.Single(); string propertyKey = "property1"; Assert.AreEqual(propertyKey, prop.Key, $"The key \"property1\" did not match for device {deviceId}"); Assert.AreEqual(message.Properties[propertyKey], prop.Value, $"The value of \"property1\" did not match for device {deviceId}"); received = true; } } finally { receivedMessage?.Dispose(); } } sw.Stop(); Assert.IsTrue(received, $"No message received for device {deviceId} with payload={payload} in {FaultInjection.RecoveryTimeMilliseconds}."); }
public static (Message message, string payload, string p1Value) ComposeC2dTestMessage(MsTestLogger logger) { var payload = Guid.NewGuid().ToString(); var messageId = Guid.NewGuid().ToString(); var p1Value = Guid.NewGuid().ToString(); var userId = Guid.NewGuid().ToString(); logger.Trace($"{nameof(ComposeC2dTestMessage)}: messageId='{messageId}' userId='{userId}' payload='{payload}' p1Value='{p1Value}'"); var message = new Message(Encoding.UTF8.GetBytes(payload)) { MessageId = messageId, UserId = userId, Properties = { ["property1"] = p1Value } }; return(message, payload, p1Value); }
/// <summary> /// Factory method. /// </summary> /// <param name="namePrefix"></param> /// <param name="type"></param> public static async Task <TestModule> GetTestModuleAsync(string deviceNamePrefix, string moduleNamePrefix, MsTestLogger logger) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, deviceNamePrefix).ConfigureAwait(false); string deviceName = testDevice.Id; string moduleName = moduleNamePrefix + Guid.NewGuid(); using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); logger.Trace($"{nameof(GetTestModuleAsync)}: Creating module for device {deviceName}."); var requestModule = new Module(deviceName, moduleName); Module module = await rm.AddModuleAsync(requestModule).ConfigureAwait(false); await rm.CloseAsync().ConfigureAwait(false); var ret = new TestModule(module); logger.Trace($"{nameof(GetTestModuleAsync)}: Using device {ret.DeviceId} with module {ret.Id}."); return(ret); }
public static async Task <Task> SubscribeAndUnsubscribeMethodAsync(DeviceClient deviceClient, string methodName, MsTestLogger logger) { var methodCallReceived = new TaskCompletionSource <bool>(); await deviceClient .SetMethodHandlerAsync( methodName, (request, context) => { logger.Trace($"{nameof(SubscribeAndUnsubscribeMethodAsync)}: DeviceClient method: {request.Name} {request.ResponseTimeout}."); return(Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(DeviceResponseJson), 200))); }, null) .ConfigureAwait(false); await deviceClient.SetMethodHandlerAsync(methodName, null, null).ConfigureAwait(false); // Return the task that tells us we have received the callback. return(methodCallReceived.Task); }
private static Task <Task> SetDeviceReceiveMethodObsoleteHandler(DeviceClient deviceClient, string methodName, MsTestLogger logger) { var methodCallReceived = new TaskCompletionSource <bool>(); #pragma warning disable CS0618 deviceClient.SetMethodHandler(methodName, (request, context) => { logger.Trace($"{nameof(SetDeviceReceiveMethodObsoleteHandler)}: DeviceClient method: {request.Name} {request.ResponseTimeout}."); try { Assert.AreEqual(methodName, request.Name, $"The expected method name should be {methodName} but was {request.Name}"); Assert.AreEqual(ServiceRequestJson, request.DataAsJson, $"The expected respose payload should be {ServiceRequestJson} but was {request.DataAsJson}"); methodCallReceived.SetResult(true); } catch (Exception ex) { methodCallReceived.SetException(ex); } return(Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(DeviceResponseJson), 200))); }, null); #pragma warning restore CS0618 return(Task.FromResult <Task>(methodCallReceived.Task)); }
public static (Client.Message message, string payload, string p1Value) ComposeD2cTestMessage(MsTestLogger logger) { string messageId = Guid.NewGuid().ToString(); string payload = Guid.NewGuid().ToString(); string p1Value = Guid.NewGuid().ToString(); string userId = Guid.NewGuid().ToString(); logger.Trace($"{nameof(ComposeD2cTestMessage)}: messageId='{messageId}' userId='{userId}' payload='{payload}' p1Value='{p1Value}'"); var message = new Client.Message(Encoding.UTF8.GetBytes(payload)) { MessageId = messageId, UserId = userId, }; message.Properties.Add("property1", p1Value); message.Properties.Add("property2", null); return(message, payload, p1Value); }
// 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 activated."); // 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 activated."); // 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 static (Client.Message message, string payload, string p1Value) ComposeD2cTestMessageOfSpecifiedSize(int messageSize, MsTestLogger logger) { string messageId = Guid.NewGuid().ToString(); string payload = $"{Guid.NewGuid()}_{new string('*', messageSize)}"; string p1Value = Guid.NewGuid().ToString(); logger.Trace($"{nameof(ComposeD2cTestMessageOfSpecifiedSize)}: messageId='{messageId}' payload='{payload}' p1Value='{p1Value}'"); var message = new Client.Message(Encoding.UTF8.GetBytes(payload)) { MessageId = messageId, }; message.Properties.Add("property1", p1Value); message.Properties.Add("property2", null); return(message, payload, p1Value); }
public static async Task SendSingleMessageAsync(DeviceClient deviceClient, string deviceId, MsTestLogger logger, int messageSize = 0) { Client.Message testMessage; if (messageSize == 0) { (testMessage, _, _) = ComposeD2cTestMessage(logger); } else { (testMessage, _, _) = ComposeD2cTestMessageOfSpecifiedSize(messageSize, logger); } using (testMessage) { await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false); } }
public TestDeviceCallbackHandler(DeviceClient deviceClient, TestDevice testDevice, MsTestLogger logger) { _deviceClient = deviceClient; _testDevice = testDevice; _logger = logger; }
public const int TestSuccessRate = 80; // 4 out of 5 (80%) test runs should pass (even after accounting for network instability issues). public static async Task TestPoolAmqpAsync( string devicePrefix, Client.TransportType transport, int poolSize, int devicesCount, Func <DeviceClient, TestDevice, Task> initOperation, Func <DeviceClient, TestDevice, Task> testOperation, Func <Task> cleanupOperation, ConnectionStringAuthScope authScope, bool ignoreConnectionStatus, MsTestLogger logger) { var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(transport) { AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings() { MaxPoolSize = unchecked ((uint)poolSize), Pooling = true } } }; int totalRuns = 0; int successfulRuns = 0; int currentSuccessRate = 0; bool reRunTest = false; IList <TestDevice> testDevices = new List <TestDevice>(); IList <DeviceClient> deviceClients = new List <DeviceClient>(); IList <AmqpConnectionStatusChange> amqpConnectionStatuses = new List <AmqpConnectionStatusChange>(); IList <Task> operations = new List <Task>(); do { totalRuns++; // Arrange // Initialize the test device client instances // Set the device client connection status change handler logger.Trace($">>> {nameof(PoolingOverAmqp)} Initializing Device Clients for multiplexing test - Test run {totalRuns}"); for (int i = 0; i < devicesCount; i++) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, $"{devicePrefix}_{i}_").ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings, authScope); var amqpConnectionStatusChange = new AmqpConnectionStatusChange(logger); deviceClient.SetConnectionStatusChangesHandler(amqpConnectionStatusChange.ConnectionStatusChangesHandler); testDevices.Add(testDevice); deviceClients.Add(deviceClient); amqpConnectionStatuses.Add(amqpConnectionStatusChange); if (initOperation != null) { operations.Add(initOperation(deviceClient, testDevice)); } } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); try { for (int i = 0; i < devicesCount; i++) { operations.Add(testOperation(deviceClients[i], testDevices[i])); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); // Close the device client instances and verify the connection status change checks bool deviceConnectionStatusAsExpected = true; for (int i = 0; i < devicesCount; i++) { await deviceClients[i].CloseAsync().ConfigureAwait(false); if (!ignoreConnectionStatus) { // The connection status change count should be 2: connect (open) and disabled (close) if (amqpConnectionStatuses[i].ConnectionStatusChangesHandlerCount != 2) { deviceConnectionStatusAsExpected = false; } // The connection status should be "Disabled", with connection status change reason "Client_close" Assert.AreEqual( ConnectionStatus.Disabled, amqpConnectionStatuses[i].LastConnectionStatus, $"The actual connection status is = {amqpConnectionStatuses[i].LastConnectionStatus}"); Assert.AreEqual( ConnectionStatusChangeReason.Client_Close, amqpConnectionStatuses[i].LastConnectionStatusChangeReason, $"The actual connection status change reason is = {amqpConnectionStatuses[i].LastConnectionStatusChangeReason}"); } } if (deviceConnectionStatusAsExpected) { successfulRuns++; } currentSuccessRate = (int)((double)successfulRuns / totalRuns * 100); reRunTest = currentSuccessRate < TestSuccessRate; } finally { // Close the service-side components and dispose the device client instances. if (cleanupOperation != null) { await cleanupOperation().ConfigureAwait(false); } foreach (DeviceClient deviceClient in deviceClients) { deviceClient.Dispose(); } // Clean up the local lists testDevices.Clear(); deviceClients.Clear(); amqpConnectionStatuses.Clear(); } } while (reRunTest && totalRuns < MaxTestRunCount); Assert.IsFalse(reRunTest, $"Device client instances got disconnected in {totalRuns - successfulRuns} runs out of {totalRuns}; current testSuccessRate = {currentSuccessRate}%."); }
public static async Task SendBatchMessagesAsync(DeviceClient deviceClient, string deviceId, MsTestLogger logger) { var messagesToBeSent = new Dictionary <Client.Message, Tuple <string, string> >(); try { var props = new List <Tuple <string, string> >(); for (int i = 0; i < MessageBatchCount; i++) { (Client.Message testMessage, string payload, string p1Value) = ComposeD2cTestMessage(logger); messagesToBeSent.Add(testMessage, Tuple.Create(payload, p1Value)); } await deviceClient.SendEventBatchAsync(messagesToBeSent.Keys.ToList()).ConfigureAwait(false); } finally { foreach (KeyValuePair <Client.Message, Tuple <string, string> > messageEntry in messagesToBeSent) { Client.Message message = messageEntry.Key; message.Dispose(); } } }