public static Exception GetExceptionFromOutcome(Outcome outcome) { Exception retException; if (outcome == null) { retException = new IotHubException("Unknown error."); return(retException); } if (outcome.DescriptorCode == Rejected.Code) { var rejected = (Rejected)outcome; retException = ToIotHubClientContract(rejected.Error); } else if (outcome.DescriptorCode == Released.Code) { retException = new OperationCanceledException("AMQP link released."); } else { retException = new IotHubException("Unknown error."); } return(retException); }
async Task ProcessConnectAckAsync(IChannelHandlerContext context, ConnAckPacket packet) { if (packet.ReturnCode != ConnectReturnCode.Accepted) { string reason = "CONNECT failed: " + packet.ReturnCode; var iotHubException = new UnauthorizedException(reason); ShutdownOnError(context, iotHubException); return; } if (!this.IsInState(StateFlags.Connecting)) { string reason = "CONNECT has been received, however a session has already been established. Only one CONNECT/CONNACK pair is expected per session."; var iotHubException = new IotHubException(reason); ShutdownOnError(context, iotHubException); return; } this.stateFlags = StateFlags.Connected; this.mqttIotHubEventHandler.OnConnected(); this.ResumeReadingIfNecessary(context); if (packet.SessionPresent) { await this.SubscribeAsync(context, null); } }
private void HandleConnectionStatusExceptions(IotHubException hubException) { ConnectionStatusChangeReason status = ConnectionStatusChangeReason.Communication_Error; if (hubException.IsTransient) { status = ConnectionStatusChangeReason.Retry_Expired; } else if (hubException is UnauthorizedException) { status = ConnectionStatusChangeReason.Bad_Credential; } else if (hubException is DeviceDisabledException) { // This mapping along with the DeviceDisabledException class // needs to be removed because DeviceDisabledException is not used anywhere. status = ConnectionStatusChangeReason.Device_Disabled; } else if (hubException is DeviceNotFoundException) { status = ConnectionStatusChangeReason.Device_Disabled; } _onConnectionStatusChanged(ConnectionStatus.Disconnected, status); }
/// <summary> /// Checks if the provided exception is considered transient in nature or not /// Transient include issues such as a single failed network attempt /// </summary> /// <param name="originalException"></param> /// <returns></returns> private static bool IsTransient(Exception originalException) { // If the exception is a IotHubException its IsTransient property can be inspected IotHubException iotHubException = originalException as IotHubException; if (iotHubException != null) { return(iotHubException.IsTransient); } // If the exception is an HTTP request exception then assume it is transient HttpRequestException httpException = originalException as HttpRequestException; if (httpException != null) { return(true); } WebException webException = originalException as WebException; if (webException != null) { // If the web exception contains one of the following status values it may be transient. return(new[] { WebExceptionStatus.ConnectionClosed, WebExceptionStatus.Timeout, WebExceptionStatus.RequestCanceled }.Contains(webException.Status)); } return(false); }
public async Task TestHandleFailOverExceptions() { // Arrange var symbol = new Amqp.Encoding.AmqpSymbol("com.microsoft:iot-hub-not-found-error"); var failOverException = new IotHubException(new Amqp.AmqpException(symbol, $"(condition='{symbol}')")); var messageConverter = Mock.Of <IMessageConverter <Message> >(m => m.FromMessage(It.IsAny <IMessage>()) == new Message()); var messageConverterProvider = Mock.Of <IMessageConverterProvider>(m => m.Get <Message>() == messageConverter); string clientId = "d1"; var cloudListener = Mock.Of <ICloudListener>(); TimeSpan idleTimeout = TimeSpan.FromSeconds(60); Action <string, CloudConnectionStatus> connectionStatusChangedHandler = (s, status) => { }; var client = new Mock <IClient>(MockBehavior.Strict); client.Setup(c => c.SendEventAsync(It.IsAny <Message>())).ThrowsAsync(failOverException); client.Setup(c => c.CloseAsync()).Returns(Task.CompletedTask); var cloudProxy = new CloudProxy(client.Object, messageConverterProvider, clientId, connectionStatusChangedHandler, cloudListener, idleTimeout, false); IMessage message = new EdgeMessage.Builder(new byte[0]).Build(); // Act await Assert.ThrowsAsync <IotHubException>(() => cloudProxy.SendMessageAsync(message)); // Assert. client.VerifyAll(); }
/// <summary> /// Handles IoT Hub exception and transform it as an <see cref="ApiException"/> with a well-known application error code. /// </summary> /// <param name="deviceId"></param> /// <param name="iotHubException"></param> /// <param name="apiException"></param> /// <returns></returns> private bool TryHandleIotHubException(string deviceId, IotHubException iotHubException, out ApiException apiException) { switch (iotHubException) { // thrown when there's no IoT Hub device registration OR there's an enabled IoT Hub device registration but device did not establish connection yet case DeviceNotFoundException ex: apiException = new IdNotFoundException(ErrorCodes.DeviceNotFound, deviceId, ex); break; // thrown when an attempt to communicate with the IoT Hub fails case IotHubCommunicationException ex: m_Logger.LogWarning($"An IotHubCommunicationException occurred: {ex}"); apiException = new CommunicationException(ErrorCodes.CommunicationError, ex.Message, ex); break; // thrown when the IoT Hub returns an error code (i.e. device registration is disable which prevent the actual device from establishing a connection) case ServerErrorException ex: m_Logger.LogWarning($"A ServerErrorException occurred: {ex}"); apiException = new CommunicationException(ErrorCodes.GatewayError, ErrorMessages.GetGatewayErrorMessage(), ex); break; // thrown when the maximum number of IoT Hub messages has been reached case QuotaExceededException ex: apiException = new CommunicationException(ErrorCodes.QuotaExceeded, ErrorMessages.GetQuotaExceededErrorMessage(), ex); break; // thrown when the message size is greater than the max size allowed (131072 bytes) case MessageTooLargeException ex: apiException = new InvalidResultException(ErrorCodes.MessageTooLarge, ErrorMessages.GetMessageTooLargeErrorMessage(), ex); break; // thrown when an error occurs during device client operation (i.e. device doesn't repond within the configured time out) // shall always be kept last case IotHubException ex: m_Logger.LogWarning($"An IotHubException occurred: {ex}"); apiException = new OperationException(ErrorCodes.DeviceOperationError, ErrorMessages.GetOperationErrorMessage(), ex); break; // exception won't be transformed and therefore will be logged accordingly default: apiException = null; break; } return(apiException != null); }
private void HandleConnectionStatusExceptions(IotHubException hubException) { ConnectionStatusChangeReason status = ConnectionStatusChangeReason.Communication_Error; if (hubException.IsTransient) { status = ConnectionStatusChangeReason.Retry_Expired; } else if (hubException is UnauthorizedException) { status = ConnectionStatusChangeReason.Bad_Credential; } else if (hubException is DeviceDisabledException) { status = ConnectionStatusChangeReason.Device_Disabled; } _onConnectionStatusChanged(ConnectionStatus.Disconnected, status); }
public async Task RetryTransientErrorThrownAfterNumberOfRetriesThrows() { var contextMock = Substitute.For <IPipelineContext>(); var innerHandlerMock = Substitute.For <IDelegatingHandler>(); innerHandlerMock.OpenAsync(Arg.Any <CancellationToken>()).Returns(t => { throw new IotHubException(TestExceptionMessage, isTransient: true); }); var sut = new RetryDelegatingHandler(contextMock, innerHandlerMock); using (var cts = new CancellationTokenSource(100)) { IotHubException exception = await sut.OpenAsync(cts.Token).ExpectedAsync <IotHubException>().ConfigureAwait(false); Assert.AreEqual(TestExceptionMessage, exception.Message); } }
public async Task RetryTransientErrorThrownAfterNumberOfRetriesThrows() { // arrange using var cts = new CancellationTokenSource(100); var contextMock = Substitute.For <PipelineContext>(); contextMock.ConnectionStatusChangesHandler = new ConnectionStatusChangesHandler(delegate(ConnectionStatus status, ConnectionStatusChangeReason reason) { }); var innerHandlerMock = Substitute.For <IDelegatingHandler>(); innerHandlerMock .OpenAsync(cts.Token) .Returns(t => throw new IotHubException(TestExceptionMessage, isTransient: true)); var sut = new RetryDelegatingHandler(contextMock, innerHandlerMock); IotHubException exception = await sut .OpenAsync(cts.Token) .ExpectedAsync <IotHubException>() .ConfigureAwait(false); // act // assert exception.Message.Should().Be(TestExceptionMessage); }
void ProcessConnectAck(IChannelHandlerContext context, ConnAckPacket packet) { if (packet.ReturnCode != ConnectReturnCode.Accepted) { string reason = "CONNECT failed: " + packet.ReturnCode; var iotHubException = new IotHubException(reason); ShutdownOnError(context, iotHubException); return; } if (!this.IsInState(StateFlags.Connecting)) { string reason = "CONNECT has been received, however a session has already been established. Only one CONNECT/CONNACK pair is expected per session."; var iotHubException = new IotHubException(reason); ShutdownOnError(context, iotHubException); return; } this.stateFlags = StateFlags.Connected; this.ResumeReadingIfNecessary(context); this.onConnected(); }
static MessagingException ComposeIotHubCommunicationException(IotHubException ex) { return(new MessagingException(ex.Message, ex.InnerException, ex.IsTransient, ex.TrackingId)); }
private void OnTwinChangesReceived(AmqpMessage amqpMessage) { if (Logging.IsEnabled) { Logging.Enter(this, amqpMessage, $"{nameof(OnTwinChangesReceived)}"); } try { _receivingAmqpLink.DisposeDelivery(amqpMessage, true, AmqpIoTConstants.AcceptedOutcome); string correlationId = amqpMessage.Properties?.CorrelationId?.ToString(); int status = GetStatus(amqpMessage); Twin twin = null; TwinCollection twinProperties = null; if (status >= 400) { // Handle failures if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase) || correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase)) { string error = null; using (var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8)) { error = reader.ReadToEnd(); }; // Retry for Http status code request timeout, Too many requests and server errors var exception = new IotHubException(error, status >= 500 || status == 429 || status == 408); _onTwinMessageReceived.Invoke(null, correlationId, null, exception); } } else { if (correlationId == null) { // Here we are getting desired property update notifications and want to handle it first using var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8); string patch = reader.ReadToEnd(); twinProperties = JsonConvert.DeserializeObject <TwinCollection>(patch); } else if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase)) { // This a response of a GET TWIN so return (set) the full twin using var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8); string body = reader.ReadToEnd(); var properties = JsonConvert.DeserializeObject <TwinProperties>(body); twin = new Twin(properties); } else if (correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase)) { // This can be used to coorelate success response with updating reported properties // However currently we do not have it as request response style implementation Logging.Info("Updated twin reported properties successfully", nameof(OnTwinChangesReceived)); } else if (correlationId.StartsWith(AmqpTwinMessageType.Put.ToString(), StringComparison.OrdinalIgnoreCase)) { // This is an acknowledgement received from service for subscribing to desired property updates Logging.Info("Subscribed for twin successfully", nameof(OnTwinChangesReceived)); } else { // This shouldn't happen Logging.Info("Received a correlation Id for Twin operation that does not match Get, Patch or Put request", nameof(OnTwinChangesReceived)); } _onTwinMessageReceived.Invoke(twin, correlationId, twinProperties, null); } } finally { if (Logging.IsEnabled) { Logging.Exit(this, amqpMessage, $"{nameof(OnTwinChangesReceived)}"); } } }
public static Exception ToIotHubClientContract(Error error) { Exception retException; if (error == null) { retException = new IotHubException("Unknown error."); return(retException); } string message = error.Description; string trackingId = null; if (error.Info != null && error.Info.TryGetValue(AmqpIotConstants.TrackingId, out trackingId)) { message = "{0}{1}{2}".FormatInvariant(message, Environment.NewLine, "Tracking Id:" + trackingId); } if (error.Condition.Equals(TimeoutError)) { retException = new TimeoutException(message); } else if (error.Condition.Equals(AmqpErrorCode.NotFound)) { retException = new DeviceNotFoundException(message, (Exception)null); } else if (error.Condition.Equals(AmqpErrorCode.NotImplemented)) { retException = new NotSupportedException(message); } else if (error.Condition.Equals(MessageLockLostError)) { retException = new DeviceMessageLockLostException(message); } else if (error.Condition.Equals(AmqpErrorCode.NotAllowed)) { retException = new InvalidOperationException(message); } else if (error.Condition.Equals(AmqpErrorCode.UnauthorizedAccess)) { retException = new UnauthorizedException(message); } else if (error.Condition.Equals(ArgumentError)) { retException = new ArgumentException(message); } else if (error.Condition.Equals(ArgumentOutOfRangeError)) { retException = new ArgumentOutOfRangeException(message); } else if (error.Condition.Equals(AmqpErrorCode.MessageSizeExceeded)) { retException = new MessageTooLargeException(message); } else if (error.Condition.Equals(AmqpErrorCode.ResourceLimitExceeded)) { // Note: The DeviceMaximumQueueDepthExceededException is not supposed to be thrown here as it is being mapped to the incorrect error code // Error code 403004 is only applicable to C2D (Service client); see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-troubleshoot-error-403004-devicemaximumqueuedepthexceeded // Error code 403002 is applicable to D2C (Device client); see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-troubleshoot-error-403002-iothubquotaexceeded // We have opted not to change the exception type thrown here since it will be a breaking change, alternatively, we are adding the correct exception type // as the inner exception. retException = new DeviceMaximumQueueDepthExceededException( $"Please check the inner exception for more information.\n " + $"The correct exception type is `{nameof(QuotaExceededException)}` " + $"but since that is a breaking change to the current behavior in the SDK, you can refer to the inner exception " + $"for more information. Exception message: {message}", new QuotaExceededException(message)); } else if (error.Condition.Equals(DeviceContainerThrottled)) { retException = new IotHubThrottledException(message, null); } else if (error.Condition.Equals(IotHubSuspended)) { retException = new IotHubSuspendedException(message); } else { retException = new IotHubException(message); } if (trackingId != null && retException is IotHubException) { var iotHubException = (IotHubException)retException; iotHubException.TrackingId = trackingId; } return(retException); }
public static Exception ToIotHubClientContract(Error error) { Exception retException; if (error == null) { retException = new IotHubException("Unknown error."); return(retException); } string message = error.Description; string trackingId = null; if (error.Info != null && error.Info.TryGetValue(TrackingId, out trackingId)) { message = "{0}{1}{2}".FormatInvariant(message, Environment.NewLine, "Tracking Id:" + trackingId); } if (error.Condition.Equals(TimeoutError)) { retException = new TimeoutException(message); } else if (error.Condition.Equals(AmqpErrorCode.NotFound)) { retException = new DeviceNotFoundException(message, (Exception)null); } else if (error.Condition.Equals(AmqpErrorCode.NotImplemented)) { retException = new NotSupportedException(message); } else if (error.Condition.Equals(MessageLockLostError)) { retException = new DeviceMessageLockLostException(message); } else if (error.Condition.Equals(AmqpErrorCode.NotAllowed)) { retException = new InvalidOperationException(message); } else if (error.Condition.Equals(AmqpErrorCode.UnauthorizedAccess)) { retException = new UnauthorizedException(message); } else if (error.Condition.Equals(ArgumentError)) { retException = new ArgumentException(message); } else if (error.Condition.Equals(ArgumentOutOfRangeError)) { retException = new ArgumentOutOfRangeException(message); } else if (error.Condition.Equals(AmqpErrorCode.MessageSizeExceeded)) { retException = new MessageTooLargeException(message); } else if (error.Condition.Equals(AmqpErrorCode.ResourceLimitExceeded)) { retException = new DeviceMaximumQueueDepthExceededException(message); } else if (error.Condition.Equals(DeviceAlreadyExists)) { retException = new DeviceAlreadyExistsException(message, null); } else if (error.Condition.Equals(DeviceContainerThrottled)) { retException = new IotHubThrottledException(message, null); } else if (error.Condition.Equals(IotHubSuspended)) { retException = new IotHubSuspendedException(message); } else { retException = new IotHubException(message); } if (trackingId != null && retException is IotHubException) { IotHubException iotHubException = (IotHubException)retException; iotHubException.TrackingId = trackingId; } return(retException); }
public static MessagingException ToMessagingException(this IotHubException ex) { return(new MessagingException("Error communicating with IoT Hub. Tracking id: " + ex.TrackingId, ex, ex.IsTransient, ex.TrackingId)); }
private void TwinMessageListener(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default) { if (correlationId == null) { // This is desired property updates, so call the callback with TwinCollection. _onDesiredStatePatchListener(twinCollection); } else { if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase) || correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase)) { // For Get and Patch, complete the task. if (_twinResponseCompletions.TryRemove(correlationId, out TaskCompletionSource <Twin> task)) { if (ex == default) { task.SetResult(twin); } else { task.SetException(ex); } } else { // This can happen if we received a message from service with correlation Id that was not set by SDK or does not exist in dictionary. if (Logging.IsEnabled) { Logging.Info("Could not remove correlation id to complete the task awaiter for a twin operation.", nameof(TwinMessageListener)); } } } } }
private void OnDesiredPropertyReceived(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default) { Logging.Enter(this, twin, nameof(OnDesiredPropertyReceived)); try { _twinMessageListener?.Invoke(twin, correlationId, twinCollection, ex); } finally { Logging.Exit(this, twin, nameof(OnDesiredPropertyReceived)); } }