diff --git a/iothub/service/src/AmqpServiceClient.cs b/iothub/service/src/AmqpServiceClient.cs deleted file mode 100644 index 232f69e205..0000000000 --- a/iothub/service/src/AmqpServiceClient.cs +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Amqp; -using Microsoft.Azure.Amqp.Framing; -using Microsoft.Azure.Devices.Common; -using Microsoft.Azure.Devices.Common.Data; -using Microsoft.Azure.Devices.Common.Exceptions; -using Microsoft.Azure.Devices.Shared; -using AmqpTrace = Microsoft.Azure.Amqp.AmqpTrace; - -namespace Microsoft.Azure.Devices -{ - // This class uses a combination of AMQP and HTTP clients to perform operations. - internal sealed class AmqpServiceClient : ServiceClient - { - private const string StatisticsUriFormat = "/statistics/service?" + ClientApiVersionHelper.ApiVersionQueryString; - private const string PurgeMessageQueueFormat = "/devices/{0}/commands?" + ClientApiVersionHelper.ApiVersionQueryString; - private const string DeviceMethodUriFormat = "/twins/{0}/methods?" + ClientApiVersionHelper.ApiVersionQueryString; - private const string ModuleMethodUriFormat = "/twins/{0}/modules/{1}/methods?" + ClientApiVersionHelper.ApiVersionQueryString; - private const string SendingPath = "/messages/deviceBound"; - - private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); - - private readonly FaultTolerantAmqpObject _faultTolerantSendingLink; - private readonly AmqpFeedbackReceiver _feedbackReceiver; - private readonly AmqpFileNotificationReceiver _fileNotificationReceiver; - private readonly IHttpClientHelper _httpClientHelper; - private readonly string _iotHubName; - private readonly ServiceClientOptions _clientOptions; - - private int _sendingDeliveryTag; - - public AmqpServiceClient( - IotHubConnectionProperties connectionProperties, - bool useWebSocketOnly, - ServiceClientTransportSettings transportSettings, - ServiceClientOptions options) - { - var iotHubConnection = new IotHubConnection(connectionProperties, useWebSocketOnly, transportSettings); - Connection = iotHubConnection; - OpenTimeout = IotHubConnection.DefaultOpenTimeout; - OperationTimeout = IotHubConnection.DefaultOperationTimeout; - _faultTolerantSendingLink = new FaultTolerantAmqpObject(CreateSendingLinkAsync, Connection.CloseLink); - _feedbackReceiver = new AmqpFeedbackReceiver(Connection); - _fileNotificationReceiver = new AmqpFileNotificationReceiver(Connection); - _iotHubName = connectionProperties.IotHubName; - _clientOptions = options; - _httpClientHelper = new HttpClientHelper( - connectionProperties.HttpsEndpoint, - connectionProperties, - ExceptionHandlingHelper.GetDefaultErrorMapping(), - s_defaultOperationTimeout, - transportSettings.HttpProxy, - transportSettings.ConnectionLeaseTimeoutMilliseconds); - - // Set the trace provider for the AMQP library. - AmqpTrace.Provider = new AmqpTransportLog(); - } - - internal AmqpServiceClient(IHttpClientHelper httpClientHelper) : base() - { - _httpClientHelper = httpClientHelper; - } - - internal AmqpServiceClient(IotHubConnection iotHubConnection, IHttpClientHelper httpClientHelper) - { - Connection = iotHubConnection; - _faultTolerantSendingLink = new FaultTolerantAmqpObject(CreateSendingLinkAsync, iotHubConnection.CloseLink); - _feedbackReceiver = new AmqpFeedbackReceiver(iotHubConnection); - _fileNotificationReceiver = new AmqpFileNotificationReceiver(iotHubConnection); - _httpClientHelper = httpClientHelper; - } - - public TimeSpan OpenTimeout { get; private set; } - - public TimeSpan OperationTimeout { get; private set; } - - public IotHubConnection Connection { get; private set; } - - // This call is executed over AMQP. - public override async Task OpenAsync() - { - Logging.Enter(this, $"Opening AmqpServiceClient", nameof(OpenAsync)); - - await _faultTolerantSendingLink.OpenAsync(OpenTimeout).ConfigureAwait(false); - await _feedbackReceiver.OpenAsync().ConfigureAwait(false); - - Logging.Exit(this, $"Opening AmqpServiceClient", nameof(OpenAsync)); - } - - // This call is executed over AMQP. - public async override Task CloseAsync() - { - Logging.Enter(this, $"Closing AmqpServiceClient", nameof(CloseAsync)); - - await _faultTolerantSendingLink.CloseAsync().ConfigureAwait(false); - await _feedbackReceiver.CloseAsync().ConfigureAwait(false); - await _fileNotificationReceiver.CloseAsync().ConfigureAwait(false); - await Connection.CloseAsync().ConfigureAwait(false); - - Logging.Exit(this, $"Closing AmqpServiceClient", nameof(CloseAsync)); - } - - // This call is executed over AMQP. - public async override Task SendAsync(string deviceId, Message message, TimeSpan? timeout = null) - { - Logging.Enter(this, $"Sending message with Id [{message?.MessageId}] for device {deviceId}", nameof(SendAsync)); - - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentNullException(nameof(deviceId)); - } - - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - if (_clientOptions?.SdkAssignsMessageId == SdkAssignsMessageId.WhenUnset && message.MessageId == null) - { - message.MessageId = Guid.NewGuid().ToString(); - } - - if (message.IsBodyCalled) - { - message.ResetBody(); - } - - timeout ??= OperationTimeout; - - using AmqpMessage amqpMessage = MessageConverter.MessageToAmqpMessage(message); - amqpMessage.Properties.To = "/devices/" + WebUtility.UrlEncode(deviceId) + "/messages/deviceBound"; - - try - { - SendingAmqpLink sendingLink = await GetSendingLinkAsync().ConfigureAwait(false); - Outcome outcome = await sendingLink - .SendMessageAsync(amqpMessage, IotHubConnection.GetNextDeliveryTag(ref _sendingDeliveryTag), AmqpConstants.NullBinary, timeout.Value) - .ConfigureAwait(false); - - Logging.Info(this, $"Outcome was: {outcome?.DescriptorName}", nameof(SendAsync)); - - if (outcome.DescriptorCode != Accepted.Code) - { - throw AmqpErrorMapper.GetExceptionFromOutcome(outcome); - } - } - catch (Exception ex) when (!(ex is TimeoutException) && !ex.IsFatal()) - { - Logging.Error(this, $"{nameof(SendAsync)} threw an exception: {ex}", nameof(SendAsync)); - throw AmqpClientHelper.ToIotHubClientContract(ex); - } - finally - { - Logging.Exit(this, $"Sending message [{message?.MessageId}] for device {deviceId}", nameof(SendAsync)); - } - } - - // This call is executed over HTTP. - public override Task PurgeMessageQueueAsync(string deviceId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Purging message queue for device: {deviceId}", nameof(PurgeMessageQueueAsync)); - - try - { - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new DeviceNotFoundException(deviceId)) } - }; - - return _httpClientHelper.DeleteAsync(GetPurgeMessageQueueAsyncUri(deviceId), errorMappingOverrides, null, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(PurgeMessageQueueAsync)} threw an exception: {ex}", nameof(PurgeMessageQueueAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Purging message queue for device: {deviceId}", nameof(PurgeMessageQueueAsync)); - } - } - - // This call is executed over AMQP. - public override FeedbackReceiver GetFeedbackReceiver() - { - return _feedbackReceiver; - } - - // This call is executed over AMQP. - public override FileNotificationReceiver GetFileNotificationReceiver() - { - return _fileNotificationReceiver; - } - - // This call is executed over HTTP. - public override Task GetServiceStatisticsAsync(CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting service statistics", nameof(GetServiceStatisticsAsync)); - - try - { - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } - }; - - return _httpClientHelper.GetAsync(GetStatisticsUri(), errorMappingOverrides, null, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetServiceStatisticsAsync)} threw an exception: {ex}", nameof(GetServiceStatisticsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting service statistics", nameof(GetServiceStatisticsAsync)); - } - } - - // This call is executed over HTTP. - public override Task InvokeDeviceMethodAsync(string deviceId, - CloudToDeviceMethod cloudToDeviceMethod, - CancellationToken cancellationToken) - { - return InvokeDeviceMethodAsync(GetDeviceMethodUri(deviceId), cloudToDeviceMethod, cancellationToken); - } - - // This call is executed over HTTP. - private Task InvokeDeviceMethodAsync(Uri uri, - CloudToDeviceMethod cloudToDeviceMethod, - CancellationToken cancellationToken) - { - Logging.Enter(this, $"Invoking device method for: {uri}", nameof(InvokeDeviceMethodAsync)); - - try - { - TimeSpan timeout = GetInvokeDeviceMethodOperationTimeout(cloudToDeviceMethod); - - return _httpClientHelper.PostAsync( - uri, - cloudToDeviceMethod, - timeout, - null, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(InvokeDeviceMethodAsync)} threw an exception: {ex}", nameof(InvokeDeviceMethodAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Invoking device method for: {uri}", nameof(InvokeDeviceMethodAsync)); - } - } - - // This call is executed over HTTP. - public override Task InvokeDeviceMethodAsync(string deviceId, string moduleId, CloudToDeviceMethod cloudToDeviceMethod, CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentNullException(nameof(deviceId)); - } - - if (string.IsNullOrWhiteSpace(moduleId)) - { - throw new ArgumentNullException(nameof(moduleId)); - } - - return InvokeDeviceMethodAsync(GetModuleMethodUri(deviceId, moduleId), cloudToDeviceMethod, cancellationToken); - } - - // This call is executed over AMQP. - public override async Task SendAsync(string deviceId, string moduleId, Message message, TimeSpan? timeout = null) - { - Logging.Enter(this, $"Sending message with Id [{message?.MessageId}] for device {deviceId}, module {moduleId}", nameof(SendAsync)); - - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentNullException(nameof(deviceId)); - } - - if (string.IsNullOrWhiteSpace(moduleId)) - { - throw new ArgumentNullException(nameof(moduleId)); - } - - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - if (_clientOptions?.SdkAssignsMessageId == SdkAssignsMessageId.WhenUnset && message.MessageId == null) - { - message.MessageId = Guid.NewGuid().ToString(); - } - - if (message.IsBodyCalled) - { - message.ResetBody(); - } - - timeout ??= OperationTimeout; - - using AmqpMessage amqpMessage = MessageConverter.MessageToAmqpMessage(message); - amqpMessage.Properties.To = "/devices/" + WebUtility.UrlEncode(deviceId) + "/modules/" + WebUtility.UrlEncode(moduleId) + "/messages/deviceBound"; - try - { - SendingAmqpLink sendingLink = await GetSendingLinkAsync().ConfigureAwait(false); - Outcome outcome = await sendingLink - .SendMessageAsync( - amqpMessage, - IotHubConnection.GetNextDeliveryTag(ref _sendingDeliveryTag), - AmqpConstants.NullBinary, - timeout.Value) - .ConfigureAwait(false); - - Logging.Info(this, $"Outcome was: {outcome?.DescriptorName}", nameof(SendAsync)); - - if (outcome.DescriptorCode != Accepted.Code) - { - throw AmqpErrorMapper.GetExceptionFromOutcome(outcome); - } - } - catch (Exception ex) when (!ex.IsFatal()) - { - Logging.Error(this, $"{nameof(SendAsync)} threw an exception: {ex}", nameof(SendAsync)); - throw AmqpClientHelper.ToIotHubClientContract(ex); - } - finally - { - Logging.Exit(this, $"Sending message with Id [{message?.MessageId}] for device {deviceId}, module {moduleId}", nameof(SendAsync)); - } - } - - private async Task GetSendingLinkAsync() - { - Logging.Enter(this, $"_faultTolerantSendingLink = {_faultTolerantSendingLink?.GetHashCode()}", nameof(GetSendingLinkAsync)); - - try - { - if (!_faultTolerantSendingLink.TryGetOpenedObject(out SendingAmqpLink sendingLink)) - { - sendingLink = await _faultTolerantSendingLink.GetOrCreateAsync(OpenTimeout).ConfigureAwait(false); - } - - Logging.Info(this, $"Retrieved SendingAmqpLink [{sendingLink?.Name}]", nameof(GetSendingLinkAsync)); - - return sendingLink; - } - finally - { - Logging.Exit(this, $"_faultTolerantSendingLink = {_faultTolerantSendingLink?.GetHashCode()}", nameof(GetSendingLinkAsync)); - } - } - - private Task CreateSendingLinkAsync(TimeSpan timeout) - { - return Connection.CreateSendingLinkAsync(SendingPath, timeout); - } - - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _faultTolerantSendingLink.Dispose(); - _fileNotificationReceiver.Dispose(); - _feedbackReceiver.Dispose(); - Connection.Dispose(); - _httpClientHelper.Dispose(); - } - } - - private static TimeSpan GetInvokeDeviceMethodOperationTimeout(CloudToDeviceMethod cloudToDeviceMethod) - { - // For InvokeDeviceMethod, we need to take into account the timeouts specified - // for the Device to connect and send a response. We also need to take into account - // the transmission time for the request send/receive - var timeout = TimeSpan.FromSeconds(15); // For wire time - timeout += TimeSpan.FromSeconds(cloudToDeviceMethod.ConnectionTimeoutInSeconds ?? 0); - timeout += TimeSpan.FromSeconds(cloudToDeviceMethod.ResponseTimeoutInSeconds ?? 0); - return timeout <= s_defaultOperationTimeout ? s_defaultOperationTimeout : timeout; - } - - private static Uri GetStatisticsUri() - { - return new Uri(StatisticsUriFormat, UriKind.Relative); - } - - private static Uri GetPurgeMessageQueueAsyncUri(string deviceId) - { - return new Uri(PurgeMessageQueueFormat.FormatInvariant(deviceId), UriKind.Relative); - } - - private static Uri GetDeviceMethodUri(string deviceId) - { - deviceId = WebUtility.UrlEncode(deviceId); - return new Uri(DeviceMethodUriFormat.FormatInvariant(deviceId), UriKind.Relative); - } - - private static Uri GetModuleMethodUri(string deviceId, string moduleId) - { - deviceId = WebUtility.UrlEncode(deviceId); - moduleId = WebUtility.UrlEncode(moduleId); - return new Uri(ModuleMethodUriFormat.FormatInvariant(deviceId, moduleId), UriKind.Relative); - } - } -} diff --git a/iothub/service/src/DigitalTwin/DigitalTwinClient.cs b/iothub/service/src/DigitalTwin/DigitalTwinClient.cs index a1289d8719..9f434063d8 100644 --- a/iothub/service/src/DigitalTwin/DigitalTwinClient.cs +++ b/iothub/service/src/DigitalTwin/DigitalTwinClient.cs @@ -27,60 +27,12 @@ public class DigitalTwinClient : IDisposable private readonly IotHubGatewayServiceAPIs _client; private readonly PnpDigitalTwin _protocolLayer; - private DigitalTwinClient(string hostName, DigitalTwinServiceClientCredentials credentials, params DelegatingHandler[] handlers) - { - var httpsEndpoint = new UriBuilder(HttpsEndpointPrefix, hostName).Uri; - var httpMessageHandler = HttpClientHelper.CreateDefaultHttpMessageHandler(null, httpsEndpoint, ServicePointHelpers.DefaultConnectionLeaseTimeout); -#pragma warning disable CA2000 // Dispose objects before losing scope (httpMessageHandlerWithDelegatingHandlers is disposed when the http client owning it is disposed) - HttpMessageHandler httpMessageHandlerWithDelegatingHandlers = CreateHttpHandlerPipeline(httpMessageHandler, handlers); -#pragma warning restore CA2000 // Dispose objects before losing scope - -#pragma warning disable CA2000 // Dispose objects before losing scope (httpClient is disposed when the protocol layer client owning it is disposed) - var httpClient = new HttpClient(httpMessageHandlerWithDelegatingHandlers, true) - { - BaseAddress = httpsEndpoint - }; -#pragma warning restore CA2000 // Dispose objects before losing scope - -#pragma warning restore CA2000 // Dispose objects before losing scope - - // When this client is disposed, all the http message handlers and delegating handlers will be disposed automatically - _client = new IotHubGatewayServiceAPIs(credentials, httpClient, true); - _client.BaseUri = httpsEndpoint; - _protocolLayer = new PnpDigitalTwin(_client); - } - - // Creates a single HttpMessageHandler to construct a HttpClient with from a base httpMessageHandler and some number of custom delegating handlers - // This is almost a copy of the Microsoft.Rest.ClientRuntime library's implementation, but with the return and parameter type HttpClientHandler replaced - // with the more abstract HttpMessageHandler in order for us to set the base handler as either a SocketsHttpHandler for .net core or an HttpClientHandler otherwise - // https://github.com/Azure/azure-sdk-for-net/blob/99f4da88ab0aa01c79aa291c6c101ab94c4ac940/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L376 - private static HttpMessageHandler CreateHttpHandlerPipeline(HttpMessageHandler httpMessageHandler, params DelegatingHandler[] handlers) + /// + /// Creates an instance of , provided for unit testing purposes only. + /// Use the CreateFromConnectionString or Create method to create an instance to use the client. + /// + public DigitalTwinClient() { - // The RetryAfterDelegatingHandler should be the absolute outermost handler - // because it's extremely lightweight and non-interfering - HttpMessageHandler currentHandler = -#pragma warning disable CA2000 // Dispose objects before losing scope (delegating handler is disposed when the http client that uses it is disposed) - new RetryDelegatingHandler(new RetryAfterDelegatingHandler { InnerHandler = httpMessageHandler }); -#pragma warning restore CA2000 // Dispose objects before losing scope - - if (handlers != null) - { - for (int i = handlers.Length - 1; i >= 0; --i) - { - DelegatingHandler handler = handlers[i]; - // Non-delegating handlers are ignored since we always - // have RetryDelegatingHandler as the outer-most handler - while (handler.InnerHandler is DelegatingHandler) - { - handler = handler.InnerHandler as DelegatingHandler; - } - - handler.InnerHandler = currentHandler; - currentHandler = handlers[i]; - } - } - - return currentHandler; } /// @@ -157,7 +109,7 @@ public static DigitalTwinClient CreateFromConnectionString(string connectionStri /// The Id of the digital twin. /// The cancellation token. /// The application/json digital twin and the http response. - public async Task> GetDigitalTwinAsync(string digitalTwinId, CancellationToken cancellationToken = default) + public virtual async Task> GetDigitalTwinAsync(string digitalTwinId, CancellationToken cancellationToken = default) { using HttpOperationResponse response = await _protocolLayer.GetDigitalTwinWithHttpMessagesAsync(digitalTwinId, null, cancellationToken) .ConfigureAwait(false); @@ -179,7 +131,7 @@ public static DigitalTwinClient CreateFromConnectionString(string connectionStri /// The optional settings for this request. /// The cancellationToken. /// The http response. - public Task> UpdateDigitalTwinAsync( + public virtual Task> UpdateDigitalTwinAsync( string digitalTwinId, string digitalTwinUpdateOperations, DigitalTwinUpdateRequestOptions requestOptions = default, @@ -197,7 +149,7 @@ public static DigitalTwinClient CreateFromConnectionString(string connectionStri /// The optional settings for this request. /// The cancellationToken. /// The application/json command invocation response and the http response. - public async Task> InvokeCommandAsync( + public virtual async Task> InvokeCommandAsync( string digitalTwinId, string commandName, string payload = default, @@ -232,7 +184,7 @@ public static DigitalTwinClient CreateFromConnectionString(string connectionStri /// The optional settings for this request. /// The cancellationToken. /// The application/json command invocation response and the http response. - public async Task> InvokeComponentCommandAsync( + public virtual async Task> InvokeComponentCommandAsync( string digitalTwinId, string componentName, string commandName, @@ -274,5 +226,61 @@ protected virtual void Dispose(bool disposing) { _client?.Dispose(); } + + private DigitalTwinClient(string hostName, DigitalTwinServiceClientCredentials credentials, params DelegatingHandler[] handlers) + { + var httpsEndpoint = new UriBuilder(HttpsEndpointPrefix, hostName).Uri; + var httpMessageHandler = HttpClientHelper.CreateDefaultHttpMessageHandler(null, httpsEndpoint, ServicePointHelpers.DefaultConnectionLeaseTimeout); +#pragma warning disable CA2000 // Dispose objects before losing scope (httpMessageHandlerWithDelegatingHandlers is disposed when the http client owning it is disposed) + HttpMessageHandler httpMessageHandlerWithDelegatingHandlers = CreateHttpHandlerPipeline(httpMessageHandler, handlers); +#pragma warning restore CA2000 // Dispose objects before losing scope + +#pragma warning disable CA2000 // Dispose objects before losing scope (httpClient is disposed when the protocol layer client owning it is disposed) + var httpClient = new HttpClient(httpMessageHandlerWithDelegatingHandlers, true) + { + BaseAddress = httpsEndpoint + }; +#pragma warning restore CA2000 // Dispose objects before losing scope + +#pragma warning restore CA2000 // Dispose objects before losing scope + + // When this client is disposed, all the http message handlers and delegating handlers will be disposed automatically + _client = new IotHubGatewayServiceAPIs(credentials, httpClient, true); + _client.BaseUri = httpsEndpoint; + _protocolLayer = new PnpDigitalTwin(_client); + } + + // Creates a single HttpMessageHandler to construct a HttpClient with from a base httpMessageHandler and some number of custom delegating handlers + // This is almost a copy of the Microsoft.Rest.ClientRuntime library's implementation, but with the return and parameter type HttpClientHandler replaced + // with the more abstract HttpMessageHandler in order for us to set the base handler as either a SocketsHttpHandler for .net core or an HttpClientHandler otherwise + // https://github.com/Azure/azure-sdk-for-net/blob/99f4da88ab0aa01c79aa291c6c101ab94c4ac940/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ServiceClient.cs#L376 + private static HttpMessageHandler CreateHttpHandlerPipeline(HttpMessageHandler httpMessageHandler, params DelegatingHandler[] handlers) + { + // The RetryAfterDelegatingHandler should be the absolute outermost handler + // because it's extremely lightweight and non-interfering + HttpMessageHandler currentHandler = +#pragma warning disable CA2000 // Dispose objects before losing scope (delegating handler is disposed when the http client that uses it is disposed) + new RetryDelegatingHandler(new RetryAfterDelegatingHandler { InnerHandler = httpMessageHandler }); +#pragma warning restore CA2000 // Dispose objects before losing scope + + if (handlers != null) + { + for (int i = handlers.Length - 1; i >= 0; --i) + { + DelegatingHandler handler = handlers[i]; + // Non-delegating handlers are ignored since we always + // have RetryDelegatingHandler as the outer-most handler + while (handler.InnerHandler is DelegatingHandler) + { + handler = handler.InnerHandler as DelegatingHandler; + } + + handler.InnerHandler = currentHandler; + currentHandler = handlers[i]; + } + } + + return currentHandler; + } } } diff --git a/iothub/service/src/HttpRegistryManager.cs b/iothub/service/src/HttpRegistryManager.cs deleted file mode 100644 index 448e738784..0000000000 --- a/iothub/service/src/HttpRegistryManager.cs +++ /dev/null @@ -1,2235 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Common; -using Microsoft.Azure.Devices.Common.Exceptions; -using Microsoft.Azure.Devices.Shared; -using Newtonsoft.Json; - -namespace Microsoft.Azure.Devices -{ - internal class HttpRegistryManager : RegistryManager - { - private const string AdminUriFormat = "/$admin/{0}?{1}"; - private const string RequestUriFormat = "/devices/{0}?{1}"; - private const string JobsUriFormat = "/jobs{0}?{1}"; - private const string StatisticsUriFormat = "/statistics/devices?" + ClientApiVersionHelper.ApiVersionQueryString; - private const string DevicesRequestUriFormat = "/devices/?top={0}&{1}"; - private const string DevicesQueryUriFormat = "/devices/query?" + ClientApiVersionHelper.ApiVersionQueryString; - private const string WildcardEtag = "*"; - - private const string ContinuationTokenHeader = "x-ms-continuation"; - private const string PageSizeHeader = "x-ms-max-item-count"; - - private const string TwinUriFormat = "/twins/{0}?{1}"; - - private const string ModulesRequestUriFormat = "/devices/{0}/modules/{1}?{2}"; - private const string ModulesOnDeviceRequestUriFormat = "/devices/{0}/modules?{1}"; - private const string ModuleTwinUriFormat = "/twins/{0}/modules/{1}?{2}"; - - private const string ConfigurationRequestUriFormat = "/configurations/{0}?{1}"; - private const string ConfigurationsRequestUriFormat = "/configurations/?top={0}&{1}"; - - private const string ApplyConfigurationOnDeviceUriFormat = "/devices/{0}/applyConfigurationContent?" + ClientApiVersionHelper.ApiVersionQueryString; - - private static readonly TimeSpan s_regexTimeoutMilliseconds = TimeSpan.FromMilliseconds(500); - - private static readonly Regex s_deviceIdRegex = new Regex( - @"^[A-Za-z0-9\-:.+%_#*?!(),=@;$']{1,128}$", - RegexOptions.Compiled | RegexOptions.IgnoreCase, - s_regexTimeoutMilliseconds); - - private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); - private static readonly TimeSpan s_defaultGetDevicesOperationTimeout = TimeSpan.FromSeconds(120); - - private IHttpClientHelper _httpClientHelper; - private readonly string _iotHubName; - - internal HttpRegistryManager(IotHubConnectionProperties connectionProperties, HttpTransportSettings transportSettings) - { - _iotHubName = connectionProperties.IotHubName; - _httpClientHelper = new HttpClientHelper( - connectionProperties.HttpsEndpoint, - connectionProperties, - ExceptionHandlingHelper.GetDefaultErrorMapping(), - s_defaultOperationTimeout, - transportSettings.Proxy, - transportSettings.ConnectionLeaseTimeoutMilliseconds); - } - - // internal test helper - internal HttpRegistryManager(IHttpClientHelper httpClientHelper, string iotHubName) - { - _httpClientHelper = httpClientHelper ?? throw new ArgumentNullException(nameof(httpClientHelper)); - _iotHubName = iotHubName; - } - - public override Task OpenAsync() - { - return TaskHelpers.CompletedTask; - } - - public override Task CloseAsync() - { - return TaskHelpers.CompletedTask; - } - - public override Task AddDeviceAsync(Device device) - { - return AddDeviceAsync(device, CancellationToken.None); - } - - public override Task AddDeviceAsync(Device device, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Adding device: {device?.Id}", nameof(AddDeviceAsync)); - - try - { - EnsureInstanceNotClosed(); - - ValidateDeviceId(device); - - if (!string.IsNullOrEmpty(device.ETag)) - { - throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); - } - - ValidateDeviceAuthentication(device.Authentication, device.Id); - - NormalizeDevice(device); - - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - } - }; - - return _httpClientHelper.PutAsync(GetRequestUri(device.Id), device, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(AddDeviceAsync)} threw an exception: {ex}", nameof(AddDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Adding device: {device?.Id}", nameof(AddDeviceAsync)); - } - } - - public override Task AddModuleAsync(Module module) - { - return AddModuleAsync(module, CancellationToken.None); - } - - public override Task AddModuleAsync(Module module, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Adding module: {module?.Id}", nameof(AddModuleAsync)); - - try - { - EnsureInstanceNotClosed(); - - ValidateModuleId(module); - - if (!string.IsNullOrEmpty(module.ETag)) - { - throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); - } - - ValidateDeviceAuthentication(module.Authentication, module.DeviceId); - - // auto generate keys if not specified - if (module.Authentication == null) - { - module.Authentication = new AuthenticationMechanism(); - } - - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - { - HttpStatusCode.Conflict, - async responseMessage => new ModuleAlreadyExistsException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - { - HttpStatusCode.RequestEntityTooLarge, - async responseMessage => new TooManyModulesOnDeviceException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - } - }; - - return _httpClientHelper.PutAsync(GetModulesRequestUri(module.DeviceId, module.Id), module, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(AddModuleAsync)} threw an exception: {ex}", nameof(AddModuleAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Adding module: {module?.Id}", nameof(AddModuleAsync)); - } - } - - public override Task AddDeviceWithTwinAsync(Device device, Twin twin) - { - return AddDeviceWithTwinAsync(device, twin, CancellationToken.None); - } - - public override Task AddDeviceWithTwinAsync(Device device, Twin twin, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Adding device with twin: {device?.Id}", nameof(AddDeviceWithTwinAsync)); - - try - { - ValidateDeviceId(device); - if (!string.IsNullOrWhiteSpace(device.ETag)) - { - throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); - } - var exportImportDeviceList = new List(1); - - var exportImportDevice = new ExportImportDevice(device, ImportMode.Create) - { - Tags = twin?.Tags, - Properties = new ExportImportDevice.PropertyContainer - { - DesiredProperties = twin?.Properties.Desired, - ReportedProperties = twin?.Properties.Reported, - } - }; - - exportImportDeviceList.Add(exportImportDevice); - - return BulkDeviceOperationsAsync( - exportImportDeviceList, - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(AddDeviceWithTwinAsync)} threw an exception: {ex}", nameof(AddDeviceWithTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Adding device with twin: {device?.Id}", nameof(AddDeviceWithTwinAsync)); - } - } - - [Obsolete("Use AddDevices2Async")] - public override Task AddDevicesAsync(IEnumerable devices) - { - return AddDevicesAsync(devices, CancellationToken.None); - } - - [Obsolete("Use AddDevices2Async")] - public override Task AddDevicesAsync(IEnumerable devices, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Adding {devices?.Count()} devices", nameof(AddDevicesAsync)); - - try - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForBulkOperations(devices, ImportMode.Create), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(AddDevicesAsync)} threw an exception: {ex}", nameof(AddDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Adding {devices?.Count()} devices", nameof(AddDevicesAsync)); - } - } - - public override Task AddDevices2Async(IEnumerable devices) - { - return AddDevices2Async(devices, CancellationToken.None); - } - - public override Task AddDevices2Async(IEnumerable devices, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Adding {devices?.Count()} devices", nameof(AddDevices2Async)); - - try - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForBulkOperations(devices, ImportMode.Create), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(AddDevices2Async)} threw an exception: {ex}", nameof(AddDevices2Async)); - throw; - } - finally - { - Logging.Exit(this, $"Adding {devices?.Count()} devices", nameof(AddDevices2Async)); - } - } - - public override Task UpdateDeviceAsync(Device device) - { - return UpdateDeviceAsync(device, CancellationToken.None); - } - - public override Task UpdateDeviceAsync(Device device, bool forceUpdate) - { - return UpdateDeviceAsync(device, forceUpdate, CancellationToken.None); - } - - public override Task UpdateDeviceAsync(Device device, CancellationToken cancellationToken) - { - return UpdateDeviceAsync(device, false, cancellationToken); - } - - public override Task UpdateDeviceAsync(Device device, bool forceUpdate, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating device: {device?.Id}", nameof(UpdateDeviceAsync)); - try - { - EnsureInstanceNotClosed(); - - ValidateDeviceId(device); - - if (string.IsNullOrWhiteSpace(device.ETag) && !forceUpdate) - { - throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); - } - - ValidateDeviceAuthentication(device.Authentication, device.Id); - - NormalizeDevice(device); - - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, - { - HttpStatusCode.NotFound, async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return (Exception)new DeviceNotFoundException(responseContent, (Exception)null); - } - } - }; - - PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; - - return _httpClientHelper.PutAsync(GetRequestUri(device.Id), device, operationType, errorMappingOverrides, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateDeviceAsync)} threw an exception: {ex}", nameof(UpdateDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Updating device: {device?.Id}", nameof(UpdateDeviceAsync)); - } - } - - public override Task UpdateModuleAsync(Module module) - { - return UpdateModuleAsync(module, CancellationToken.None); - } - - public override Task UpdateModuleAsync(Module module, bool forceUpdate) - { - return UpdateModuleAsync(module, forceUpdate, CancellationToken.None); - } - - public override Task UpdateModuleAsync(Module module, CancellationToken cancellationToken) - { - return UpdateModuleAsync(module, false, CancellationToken.None); - } - - public override Task UpdateModuleAsync(Module module, bool forceUpdate, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating module: {module?.Id}", nameof(UpdateModuleAsync)); - - try - { - EnsureInstanceNotClosed(); - - ValidateModuleId(module); - - if (string.IsNullOrWhiteSpace(module.ETag) && !forceUpdate) - { - throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); - } - - ValidateDeviceAuthentication(module.Authentication, module.DeviceId); - - // auto generate keys if not specified - if (module.Authentication == null) - { - module.Authentication = new AuthenticationMechanism(); - } - - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, - { - HttpStatusCode.NotFound, async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return new ModuleNotFoundException(responseContent, (Exception)null); - } - } - }; - - PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; - - return _httpClientHelper.PutAsync(GetModulesRequestUri(module.DeviceId, module.Id), module, operationType, errorMappingOverrides, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateModuleAsync)} threw an exception: {ex}", nameof(UpdateModuleAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Updating module: {module?.Id}", nameof(UpdateModuleAsync)); - } - } - - public override Task AddConfigurationAsync(Configuration configuration) - { - return AddConfigurationAsync(configuration, CancellationToken.None); - } - - public override Task AddConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Adding configuration: {configuration?.Id}", nameof(AddConfigurationAsync)); - - try - { - EnsureInstanceNotClosed(); - - if (!string.IsNullOrEmpty(configuration.ETag)) - { - throw new ArgumentException(ApiResources.ETagSetWhileCreatingConfiguration); - } - - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - } - }; - - return _httpClientHelper.PutAsync(GetConfigurationRequestUri(configuration.Id), configuration, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(AddConfigurationAsync)} threw an exception: {ex}", nameof(AddConfigurationAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Adding configuration: {configuration?.Id}", nameof(AddConfigurationAsync)); - } - } - - public override Task GetConfigurationAsync(string configurationId) - { - return GetConfigurationAsync(configurationId, CancellationToken.None); - } - - public override Task GetConfigurationAsync(string configurationId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting configuration: {configurationId}", nameof(GetConfigurationAsync)); - try - { - if (string.IsNullOrWhiteSpace(configurationId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "configurationId")); - } - - EnsureInstanceNotClosed(); - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.NotFound, async responseMessage => new ConfigurationNotFoundException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } - }; - - return _httpClientHelper.GetAsync(GetConfigurationRequestUri(configurationId), errorMappingOverrides, null, false, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetConfigurationAsync)} threw an exception: {ex}", nameof(GetConfigurationAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Get configuration: {configurationId}", nameof(GetConfigurationAsync)); - } - } - - public override Task> GetConfigurationsAsync(int maxCount) - { - return GetConfigurationsAsync(maxCount, CancellationToken.None); - } - - public override Task> GetConfigurationsAsync(int maxCount, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting configuration: max count: {maxCount}", nameof(GetConfigurationsAsync)); - try - { - EnsureInstanceNotClosed(); - - return _httpClientHelper.GetAsync>( - GetConfigurationsRequestUri(maxCount), - null, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetConfigurationsAsync)} threw an exception: {ex}", nameof(GetConfigurationsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting configuration: max count: {maxCount}", nameof(GetConfigurationsAsync)); - } - } - - public override Task UpdateConfigurationAsync(Configuration configuration) - { - return UpdateConfigurationAsync(configuration, CancellationToken.None); - } - - public override Task UpdateConfigurationAsync(Configuration configuration, bool forceUpdate) - { - return UpdateConfigurationAsync(configuration, forceUpdate, CancellationToken.None); - } - - public override Task UpdateConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) - { - return UpdateConfigurationAsync(configuration, false, cancellationToken); - } - - public override Task UpdateConfigurationAsync(Configuration configuration, bool forceUpdate, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating configuration: {configuration?.Id} - Force update: {forceUpdate}", nameof(UpdateConfigurationAsync)); - - try - { - EnsureInstanceNotClosed(); - - if (string.IsNullOrWhiteSpace(configuration.ETag) && !forceUpdate) - { - throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingConfiguration); - } - - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, - { - HttpStatusCode.NotFound, async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return new ConfigurationNotFoundException(responseContent, (Exception)null); - } - } - }; - - PutOperationType operationType = forceUpdate - ? PutOperationType.ForceUpdateEntity - : PutOperationType.UpdateEntity; - - return _httpClientHelper.PutAsync(GetConfigurationRequestUri(configuration.Id), configuration, operationType, errorMappingOverrides, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateConfigurationAsync)} threw an exception: {ex}", nameof(UpdateConfigurationAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Updating configuration: {configuration?.Id} - Force update: {forceUpdate}", nameof(UpdateConfigurationAsync)); - } - } - - public override Task RemoveConfigurationAsync(string configurationId) - { - return RemoveConfigurationAsync(configurationId, CancellationToken.None); - } - - public override Task RemoveConfigurationAsync(string configurationId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing configuration: {configurationId}", nameof(RemoveConfigurationAsync)); - - try - { - EnsureInstanceNotClosed(); - - if (string.IsNullOrWhiteSpace(configurationId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "configurationId")); - } - - // use wild-card ETag - var eTag = new ETagHolder { ETag = "*" }; - return RemoveConfigurationAsync(configurationId, eTag, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveConfigurationAsync)} threw an exception: {ex}", nameof(RemoveConfigurationAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing configuration: {configurationId}", nameof(RemoveConfigurationAsync)); - } - } - - public override Task RemoveConfigurationAsync(Configuration configuration) - { - return RemoveConfigurationAsync(configuration, CancellationToken.None); - } - - public override Task RemoveConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing configuration: {configuration?.Id}", nameof(RemoveConfigurationAsync)); - try - { - EnsureInstanceNotClosed(); - - return string.IsNullOrWhiteSpace(configuration.ETag) - ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingConfiguration) - : RemoveConfigurationAsync(configuration.Id, configuration, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveConfigurationAsync)} threw an exception: {ex}", nameof(RemoveConfigurationAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing configuration: {configuration?.Id}", nameof(RemoveConfigurationAsync)); - } - } - - public override Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content) - { - return ApplyConfigurationContentOnDeviceAsync(deviceId, content, CancellationToken.None); - } - - public override Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Applying configuration content on device: {deviceId}", nameof(ApplyConfigurationContentOnDeviceAsync)); - - try - { - return _httpClientHelper.PostAsync(GetApplyConfigurationOnDeviceRequestUri(deviceId), content, null, null, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(ApplyConfigurationContentOnDeviceAsync)} threw an exception: {ex}", nameof(ApplyConfigurationContentOnDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Applying configuration content on device: {deviceId}", nameof(ApplyConfigurationContentOnDeviceAsync)); - } - } - - private Task RemoveConfigurationAsync(string configurationId, IETagHolder eTagHolder, CancellationToken cancellationToken) - { - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.NotFound, - async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return new ConfigurationNotFoundException(responseContent, (Exception) null); - } - }, - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - } - }; - - return _httpClientHelper.DeleteAsync(GetConfigurationRequestUri(configurationId), eTagHolder, errorMappingOverrides, null, cancellationToken); - } - - [Obsolete("Use UpdateDevices2Async")] - public override Task UpdateDevicesAsync(IEnumerable devices) - { - return UpdateDevicesAsync(devices, false, CancellationToken.None); - } - - [Obsolete("Use UpdateDevices2Async")] - public override Task UpdateDevicesAsync(IEnumerable devices, bool forceUpdate, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating multiple devices: count: {devices?.Count()}", nameof(UpdateDevicesAsync)); - - try - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForBulkOperations(devices, forceUpdate ? ImportMode.Update : ImportMode.UpdateIfMatchETag), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateDevicesAsync)} threw an exception: {ex}", nameof(UpdateDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Updating multiple devices: count: {devices?.Count()}", nameof(UpdateDevicesAsync)); - } - } - - public override Task UpdateDevices2Async(IEnumerable devices) - { - return UpdateDevices2Async(devices, false, CancellationToken.None); - } - - public override Task UpdateDevices2Async(IEnumerable devices, bool forceUpdate, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating multiple devices: count: {devices?.Count()} - Force update: {forceUpdate}", nameof(UpdateDevices2Async)); - - try - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForBulkOperations(devices, forceUpdate ? ImportMode.Update : ImportMode.UpdateIfMatchETag), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateDevices2Async)} threw an exception: {ex}", nameof(UpdateDevices2Async)); - throw; - } - finally - { - Logging.Exit(this, $"Updating multiple devices: count: {devices?.Count()} - Force update: {forceUpdate}", nameof(UpdateDevices2Async)); - } - } - - public override Task RemoveDeviceAsync(string deviceId) - { - return RemoveDeviceAsync(deviceId, CancellationToken.None); - } - - public override Task RemoveDeviceAsync(string deviceId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing device: {deviceId}", nameof(RemoveDeviceAsync)); - try - { - EnsureInstanceNotClosed(); - - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); - } - - // use wild-card ETag - var eTag = new ETagHolder { ETag = "*" }; - return RemoveDeviceAsync(deviceId, eTag, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveDeviceAsync)} threw an exception: {ex}", nameof(RemoveDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing device: {deviceId}", nameof(RemoveDeviceAsync)); - } - } - - public override Task RemoveDeviceAsync(Device device) - { - return RemoveDeviceAsync(device, CancellationToken.None); - } - - public override Task RemoveDeviceAsync(Device device, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing device: {device?.Id}", nameof(RemoveDeviceAsync)); - - try - { - EnsureInstanceNotClosed(); - - ValidateDeviceId(device); - - return string.IsNullOrWhiteSpace(device.ETag) - ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice) - : RemoveDeviceAsync(device.Id, device, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveDeviceAsync)} threw an exception: {ex}", nameof(RemoveDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing device: {device?.Id}", nameof(RemoveDeviceAsync)); - } - } - - public override Task RemoveModuleAsync(string deviceId, string moduleId) - { - return RemoveModuleAsync(deviceId, moduleId, CancellationToken.None); - } - - public override Task RemoveModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing module: device Id:{deviceId} moduleId: {moduleId}", nameof(RemoveDeviceAsync)); - - try - { - EnsureInstanceNotClosed(); - - if (string.IsNullOrWhiteSpace(deviceId) || string.IsNullOrEmpty(moduleId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); - } - - // use wild-card ETag - var eTag = new ETagHolder { ETag = "*" }; - return RemoveDeviceModuleAsync(deviceId, moduleId, eTag, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveModuleAsync)} threw an exception: {ex}", nameof(RemoveModuleAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing module: device Id:{deviceId} moduleId: {moduleId}", nameof(RemoveModuleAsync)); - } - } - - public override Task RemoveModuleAsync(Module module) - { - return RemoveModuleAsync(module, CancellationToken.None); - } - - public override Task RemoveModuleAsync(Module module, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing module: device Id:{module?.DeviceId} moduleId: {module?.Id}", nameof(RemoveModuleAsync)); - - try - { - EnsureInstanceNotClosed(); - - ValidateModuleId(module); - - return string.IsNullOrWhiteSpace(module.ETag) - ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice) - : RemoveDeviceModuleAsync(module.DeviceId, module.Id, module, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveModuleAsync)} threw an exception: {ex}", nameof(RemoveModuleAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing module: device Id:{module?.DeviceId} moduleId: {module?.Id}", nameof(RemoveModuleAsync)); - } - } - - [Obsolete("Use RemoveDevices2Async")] - public override Task RemoveDevicesAsync(IEnumerable devices) - { - return RemoveDevicesAsync(devices, false, CancellationToken.None); - } - - [Obsolete("Use RemoveDevices2Async")] - public override Task RemoveDevicesAsync(IEnumerable devices, bool forceRemove, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); - try - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForBulkOperations(devices, forceRemove ? ImportMode.Delete : ImportMode.DeleteIfMatchETag), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveDevicesAsync)} threw an exception: {ex}", nameof(RemoveDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); - } - } - - public override Task RemoveDevices2Async(IEnumerable devices) - { - return RemoveDevices2Async(devices, false, CancellationToken.None); - } - - public override Task RemoveDevices2Async(IEnumerable devices, bool forceRemove, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevices2Async)); - - try - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForBulkOperations(devices, forceRemove ? ImportMode.Delete : ImportMode.DeleteIfMatchETag), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(RemoveDevicesAsync)} threw an exception: {ex}", nameof(RemoveDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); - } - } - - public override Task GetRegistryStatisticsAsync() - { - return GetRegistryStatisticsAsync(CancellationToken.None); - } - - public override Task GetRegistryStatisticsAsync(CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting registry statistics", nameof(GetRegistryStatisticsAsync)); - - try - { - EnsureInstanceNotClosed(); - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } - }; - - return _httpClientHelper.GetAsync(GetStatisticsUri(), errorMappingOverrides, null, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetRegistryStatisticsAsync)} threw an exception: {ex}", nameof(GetRegistryStatisticsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting registry statistics", nameof(GetRegistryStatisticsAsync)); - } - } - - public override Task GetDeviceAsync(string deviceId) - { - return GetDeviceAsync(deviceId, CancellationToken.None); - } - - public override Task GetDeviceAsync(string deviceId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting device: {deviceId}", nameof(GetDeviceAsync)); - try - { - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); - } - - EnsureInstanceNotClosed(); - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } - }; - - return _httpClientHelper.GetAsync(GetRequestUri(deviceId), errorMappingOverrides, null, false, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetDeviceAsync)} threw an exception: {ex}", nameof(GetDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting device: {deviceId}", nameof(GetDeviceAsync)); - } - } - - public override Task GetModuleAsync(string deviceId, string moduleId) - { - return GetModuleAsync(deviceId, moduleId, CancellationToken.None); - } - - public override Task GetModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting module: device Id: {deviceId} - module Id: {moduleId}", nameof(GetModuleAsync)); - - try - { - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); - } - - if (string.IsNullOrWhiteSpace(moduleId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "moduleId")); - } - - EnsureInstanceNotClosed(); - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.NotFound, - responseMessage => Task.FromResult(new ModuleNotFoundException(deviceId, moduleId)) - }, - }; - - return _httpClientHelper.GetAsync(GetModulesRequestUri(deviceId, moduleId), errorMappingOverrides, null, false, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetModuleAsync)} threw an exception: {ex}", nameof(GetModuleAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting module: device Id: {deviceId} - module Id: {moduleId}", nameof(GetModuleAsync)); - } - } - - public override Task> GetModulesOnDeviceAsync(string deviceId) - { - return GetModulesOnDeviceAsync(deviceId, CancellationToken.None); - } - - public override Task> GetModulesOnDeviceAsync(string deviceId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting module on device: {deviceId}", nameof(GetModulesOnDeviceAsync)); - - try - { - EnsureInstanceNotClosed(); - - return _httpClientHelper.GetAsync>( - GetModulesOnDeviceRequestUri(deviceId), - null, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetModulesOnDeviceAsync)} threw an exception: {ex}", nameof(GetModulesOnDeviceAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting module on device: {deviceId}", nameof(GetModulesOnDeviceAsync)); - } - } - - [Obsolete("Use CreateQuery(\"select * from devices\", pageSize);")] - public override Task> GetDevicesAsync(int maxCount) - { - return GetDevicesAsync(maxCount, CancellationToken.None); - } - - [Obsolete("Use CreateQuery(\"select * from devices\", pageSize);")] - public override Task> GetDevicesAsync(int maxCount, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting devices - max count: {maxCount}", nameof(GetDevicesAsync)); - - try - { - EnsureInstanceNotClosed(); - - return _httpClientHelper.GetAsync>( - GetDevicesRequestUri(maxCount), - s_defaultGetDevicesOperationTimeout, - null, - null, - true, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetDevicesAsync)} threw an exception: {ex}", nameof(GetDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting devices - max count: {maxCount}", nameof(GetDevicesAsync)); - } - } - - public override IQuery CreateQuery(string sqlQueryString) - { - return CreateQuery(sqlQueryString, null); - } - - public override IQuery CreateQuery(string sqlQueryString, int? pageSize) - { - Logging.Enter(this, $"Creating query", nameof(CreateQuery)); - try - { - return new Query((token) => ExecuteQueryAsync( - sqlQueryString, - pageSize, - token, - CancellationToken.None)); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(CreateQuery)} threw an exception: {ex}", nameof(CreateQuery)); - throw; - } - finally - { - Logging.Exit(this, $"Creating query", nameof(CreateQuery)); - } - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing && _httpClientHelper != null) - { - _httpClientHelper.Dispose(); - _httpClientHelper = null; - } - } - - private static IEnumerable GenerateExportImportDeviceListForBulkOperations(IEnumerable devices, ImportMode importMode) - { - if (devices == null) - { - throw new ArgumentNullException(nameof(devices)); - } - - if (!devices.Any()) - { - throw new ArgumentException($"Parameter {nameof(devices)} cannot be empty."); - } - - var exportImportDeviceList = new List(devices.Count()); - foreach (Device device in devices) - { - ValidateDeviceId(device); - - switch (importMode) - { - case ImportMode.Create: - if (!string.IsNullOrWhiteSpace(device.ETag)) - { - throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); - } - break; - - case ImportMode.Update: - // No preconditions - break; - - case ImportMode.UpdateIfMatchETag: - if (string.IsNullOrWhiteSpace(device.ETag)) - { - throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); - } - break; - - case ImportMode.Delete: - // No preconditions - break; - - case ImportMode.DeleteIfMatchETag: - if (string.IsNullOrWhiteSpace(device.ETag)) - { - throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice); - } - break; - - default: - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.InvalidImportMode, importMode)); - } - - var exportImportDevice = new ExportImportDevice(device, importMode); - exportImportDeviceList.Add(exportImportDevice); - } - - return exportImportDeviceList; - } - - private static IEnumerable GenerateExportImportDeviceListForTwinBulkOperations(IEnumerable twins, ImportMode importMode) - { - if (twins == null) - { - throw new ArgumentNullException(nameof(twins)); - } - - if (!twins.Any()) - { - throw new ArgumentException($"Parameter {nameof(twins)} cannot be empty"); - } - - var exportImportDeviceList = new List(twins.Count()); - foreach (Twin twin in twins) - { - ValidateTwinId(twin); - - switch (importMode) - { - case ImportMode.UpdateTwin: - // No preconditions - break; - - case ImportMode.UpdateTwinIfMatchETag: - if (string.IsNullOrWhiteSpace(twin.ETag)) - { - throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingTwin); - } - break; - - default: - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.InvalidImportMode, importMode)); - } - - var exportImportDevice = new ExportImportDevice - { - Id = twin.DeviceId, - ModuleId = twin.ModuleId, - ImportMode = importMode, - TwinETag = importMode == ImportMode.UpdateTwinIfMatchETag ? twin.ETag : null, - Tags = twin.Tags, - Properties = new ExportImportDevice.PropertyContainer(), - }; - exportImportDevice.Properties.DesiredProperties = twin.Properties?.Desired; - - exportImportDeviceList.Add(exportImportDevice); - } - - return exportImportDeviceList; - } - - private Task BulkDeviceOperationsAsync(IEnumerable devices, string version, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Performing bulk device operation on : {devices?.Count()} devices. version: {version}", nameof(BulkDeviceOperationsAsync)); - try - { - BulkDeviceOperationSetup(devices); - - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, - { HttpStatusCode.RequestEntityTooLarge, async responseMessage => new TooManyDevicesException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, - { HttpStatusCode.BadRequest, async responseMessage => new ArgumentException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } - }; - - return _httpClientHelper.PostAsync, T>(GetBulkRequestUri(version), devices, errorMappingOverrides, null, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(BulkDeviceOperationsAsync)} threw an exception: {ex}", nameof(BulkDeviceOperationsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Performing bulk device operation on : {devices?.Count()} devices. version: {version}", nameof(BulkDeviceOperationsAsync)); - } - } - - private void BulkDeviceOperationSetup(IEnumerable devices) - { - EnsureInstanceNotClosed(); - - if (devices == null) - { - throw new ArgumentNullException(nameof(devices)); - } - - foreach (ExportImportDevice device in devices) - { - ValidateDeviceAuthentication(device.Authentication, device.Id); - - NormalizeExportImportDevice(device); - } - } - - public override Task ExportRegistryAsync(string storageAccountConnectionString, string containerName) - { - return ExportRegistryAsync(storageAccountConnectionString, containerName, CancellationToken.None); - } - - public override Task ExportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Exporting registry", nameof(ExportRegistryAsync)); - try - { - EnsureInstanceNotClosed(); - - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } - }; - - return _httpClientHelper.PostAsync( - GetAdminUri("exportRegistry"), - new ExportImportRequest - { - ContainerName = containerName, - StorageConnectionString = storageAccountConnectionString, - }, - errorMappingOverrides, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(ExportRegistryAsync)} threw an exception: {ex}", nameof(ExportRegistryAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Exporting registry", nameof(ExportRegistryAsync)); - } - } - - public override Task ImportRegistryAsync(string storageAccountConnectionString, string containerName) - { - return ImportRegistryAsync(storageAccountConnectionString, containerName, CancellationToken.None); - } - - public override Task ImportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Importing registry", nameof(ImportRegistryAsync)); - - try - { - EnsureInstanceNotClosed(); - - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } - }; - - return _httpClientHelper.PostAsync( - GetAdminUri("importRegistry"), - new ExportImportRequest - { - ContainerName = containerName, - StorageConnectionString = storageAccountConnectionString, - }, - errorMappingOverrides, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(ImportRegistryAsync)} threw an exception: {ex}", nameof(ImportRegistryAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Importing registry", nameof(ImportRegistryAsync)); - } - } - - public override Task GetJobAsync(string jobId) - { - return GetJobAsync(jobId, CancellationToken.None); - } - - public override Task> GetJobsAsync() - { - return GetJobsAsync(CancellationToken.None); - } - - public override Task CancelJobAsync(string jobId) - { - return CancelJobAsync(jobId, CancellationToken.None); - } - - public override Task ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys) - { - return ExportDevicesAsync( - JobProperties.CreateForExportJob( - exportBlobContainerUri, - excludeKeys)); - } - - public override Task ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys, CancellationToken ct) - { - return ExportDevicesAsync( - JobProperties.CreateForExportJob( - exportBlobContainerUri, - excludeKeys), - ct); - } - - public override Task ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys) - { - return ExportDevicesAsync( - JobProperties.CreateForExportJob( - exportBlobContainerUri, - excludeKeys, - outputBlobName)); - } - - public override Task ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys, CancellationToken ct) - { - return ExportDevicesAsync( - JobProperties.CreateForExportJob( - exportBlobContainerUri, - excludeKeys, - outputBlobName), - ct); - } - - public override Task ExportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default) - { - Logging.Enter(this, $"Export Job running with {jobParameters}", nameof(ExportDevicesAsync)); - - try - { - jobParameters.Type = JobType.ExportDevices; - return CreateJobAsync(jobParameters, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(ExportDevicesAsync)} threw an exception: {ex}", nameof(ExportDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Export Job running with {jobParameters}", nameof(ExportDevicesAsync)); - } - } - - public override Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri) - { - return ImportDevicesAsync( - JobProperties.CreateForImportJob( - importBlobContainerUri, - outputBlobContainerUri)); - } - - public override Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, CancellationToken ct) - { - return ImportDevicesAsync( - JobProperties.CreateForImportJob( - importBlobContainerUri, - outputBlobContainerUri), - ct); - } - - public override Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName) - { - return ImportDevicesAsync( - JobProperties.CreateForImportJob( - importBlobContainerUri, - outputBlobContainerUri, - inputBlobName)); - } - - public override Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName, CancellationToken ct) - { - return ImportDevicesAsync( - JobProperties.CreateForImportJob( - importBlobContainerUri, - outputBlobContainerUri, - inputBlobName), - ct); - } - - public override Task ImportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default) - { - Logging.Enter(this, $"Import Job running with {jobParameters}", nameof(ImportDevicesAsync)); - try - { - jobParameters.Type = JobType.ImportDevices; - return CreateJobAsync(jobParameters, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(ExportDevicesAsync)} threw an exception: {ex}", nameof(ImportDevicesAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Import Job running with {jobParameters}", nameof(ImportDevicesAsync)); - } - } - - private Task CreateJobAsync(JobProperties jobProperties, CancellationToken ct) - { - EnsureInstanceNotClosed(); - - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.Forbidden, async (responseMessage) => new JobQuotaExceededException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false))} - }; - - string clientApiVersion = ClientApiVersionHelper.ApiVersionQueryString; - - return _httpClientHelper.PostAsync( - GetJobUri("/create", clientApiVersion), - jobProperties, - errorMappingOverrides, - null, - ct); - } - - public override Task GetJobAsync(string jobId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting job {jobId}", nameof(GetJobsAsync)); - try - { - EnsureInstanceNotClosed(); - - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new JobNotFoundException(jobId)) } - }; - - return _httpClientHelper.GetAsync( - GetJobUri("/{0}".FormatInvariant(jobId)), - errorMappingOverrides, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting job {jobId}", nameof(GetJobsAsync)); - } - } - - public override Task> GetJobsAsync(CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting job", nameof(GetJobsAsync)); - try - { - EnsureInstanceNotClosed(); - - return _httpClientHelper.GetAsync>( - GetJobUri(string.Empty), - null, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting job", nameof(GetJobsAsync)); - } - } - - public override Task CancelJobAsync(string jobId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Canceling job: {jobId}", nameof(CancelJobAsync)); - try - { - EnsureInstanceNotClosed(); - - var errorMappingOverrides = new Dictionary>> - { - { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new JobNotFoundException(jobId)) } - }; - - IETagHolder jobETag = new ETagHolder - { - ETag = jobId, - }; - - return _httpClientHelper.DeleteAsync( - GetJobUri("/{0}".FormatInvariant(jobId)), - jobETag, - errorMappingOverrides, - null, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting job {jobId}", nameof(GetJobsAsync)); - } - } - - public override Task GetTwinAsync(string deviceId) - { - return GetTwinAsync(deviceId, CancellationToken.None); - } - - public override Task GetTwinAsync(string deviceId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting device twin on device: {deviceId}", nameof(GetTwinAsync)); - try - { - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); - } - - EnsureInstanceNotClosed(); - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } - }; - - return _httpClientHelper.GetAsync(GetTwinUri(deviceId), errorMappingOverrides, null, false, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetTwinAsync)} threw an exception: {ex}", nameof(GetTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting device twin on device: {deviceId}", nameof(GetTwinAsync)); - } - } - - public override Task GetTwinAsync(string deviceId, string moduleId) - { - return GetTwinAsync(deviceId, moduleId, CancellationToken.None); - } - - public override Task GetTwinAsync(string deviceId, string moduleId, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Getting device twin on device: {deviceId} and module: {moduleId}", nameof(GetTwinAsync)); - - try - { - if (string.IsNullOrWhiteSpace(deviceId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); - } - - if (string.IsNullOrWhiteSpace(moduleId)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "moduleId")); - } - - EnsureInstanceNotClosed(); - var errorMappingOverrides = new Dictionary>>() - { - { HttpStatusCode.NotFound, async responseMessage => new ModuleNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) } - }; - - return _httpClientHelper.GetAsync(GetModuleTwinRequestUri(deviceId, moduleId), errorMappingOverrides, null, false, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(GetTwinAsync)} threw an exception: {ex}", nameof(GetTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Getting device twin on device: {deviceId} and module: {moduleId}", nameof(GetTwinAsync)); - } - } - - public override Task UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag) - { - return UpdateTwinAsync(deviceId, jsonTwinPatch, etag, CancellationToken.None); - } - - public override Task UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating device twin on device: {deviceId}", nameof(UpdateTwinAsync)); - - try - { - if (string.IsNullOrWhiteSpace(jsonTwinPatch)) - { - throw new ArgumentNullException(nameof(jsonTwinPatch)); - } - - // TODO: Do we need to deserialize Twin, only to serialize it again? - Twin twin = JsonConvert.DeserializeObject(jsonTwinPatch); - return UpdateTwinAsync(deviceId, twin, etag, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Updating device twin on device: {deviceId}", nameof(UpdateTwinAsync)); - } - } - - public override Task UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag) - { - return UpdateTwinAsync(deviceId, moduleId, twinPatch, etag, CancellationToken.None); - } - - public override Task UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag, CancellationToken cancellationToken) - { - return UpdateTwinInternalAsync(deviceId, moduleId, twinPatch, etag, false, cancellationToken); - } - - public override Task UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag) - { - return UpdateTwinAsync(deviceId, moduleId, jsonTwinPatch, etag, CancellationToken.None); - } - - public override Task UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Updating device twin on device: {deviceId} and module: {moduleId}", nameof(UpdateTwinAsync)); - try - { - if (string.IsNullOrWhiteSpace(jsonTwinPatch)) - { - throw new ArgumentNullException(nameof(jsonTwinPatch)); - } - - // TODO: Do we need to deserialize Twin, only to serialize it again? - Twin twin = JsonConvert.DeserializeObject(jsonTwinPatch); - return UpdateTwinAsync(deviceId, moduleId, twin, etag, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Updating device twin on device: {deviceId} and module: {moduleId}", nameof(UpdateTwinAsync)); - } - } - - public override Task UpdateTwinAsync(string deviceId, Twin twinPatch, string etag) - { - return UpdateTwinAsync(deviceId, twinPatch, etag, CancellationToken.None); - } - - public override Task UpdateTwinAsync(string deviceId, Twin twinPatch, string etag, CancellationToken cancellationToken) - { - return UpdateTwinInternalAsync(deviceId, twinPatch, etag, false, cancellationToken); - } - - public override Task ReplaceTwinAsync(string deviceId, string newTwinJson, string etag) - { - return ReplaceTwinAsync(deviceId, newTwinJson, etag, CancellationToken.None); - } - - public override Task ReplaceTwinAsync(string deviceId, string newTwinJson, string etag, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Replacing device twin on device: {deviceId}", nameof(ReplaceTwinAsync)); - try - { - if (string.IsNullOrWhiteSpace(newTwinJson)) - { - throw new ArgumentNullException(nameof(newTwinJson)); - } - - // TODO: Do we need to deserialize Twin, only to serialize it again? - Twin twin = JsonConvert.DeserializeObject(newTwinJson); - return ReplaceTwinAsync(deviceId, twin, etag, cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(ReplaceTwinAsync)} threw an exception: {ex}", nameof(ReplaceTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Replacing device twin on device: {deviceId}", nameof(ReplaceTwinAsync)); - } - } - - public override Task ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag) - { - return ReplaceTwinAsync(deviceId, moduleId, newTwin, etag, CancellationToken.None); - } - - public override Task ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag, CancellationToken cancellationToken) - { - return UpdateTwinInternalAsync(deviceId, moduleId, newTwin, etag, true, cancellationToken); - } - - public override Task ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag) - { - return ReplaceTwinAsync(deviceId, moduleId, newTwinJson, etag, CancellationToken.None); - } - - public override Task ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag, CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(newTwinJson)) - { - throw new ArgumentNullException(nameof(newTwinJson)); - } - - // TODO: Do we need to deserialize Twin, only to serialize it again? - Twin twin = JsonConvert.DeserializeObject(newTwinJson); - return ReplaceTwinAsync(deviceId, moduleId, twin, etag, cancellationToken); - } - - public override Task ReplaceTwinAsync(string deviceId, Twin newTwin, string etag) - { - return ReplaceTwinAsync(deviceId, newTwin, etag, CancellationToken.None); - } - - public override Task ReplaceTwinAsync(string deviceId, Twin newTwin, string etag, CancellationToken cancellationToken) - { - return UpdateTwinInternalAsync(deviceId, newTwin, etag, true, cancellationToken); - } - - private Task UpdateTwinInternalAsync(string deviceId, Twin twin, string etag, bool isReplace, CancellationToken cancellationToken) - { - EnsureInstanceNotClosed(); - - if (twin != null) - { - twin.DeviceId = deviceId; - } - - ValidateTwinId(twin); - - if (string.IsNullOrEmpty(etag)) - { - throw new ArgumentNullException(nameof(etag)); - } - - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - { - HttpStatusCode.NotFound, - async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) - } - }; - - return isReplace - ? _httpClientHelper.PutAsync( - GetTwinUri(deviceId), - twin, - etag, - etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, - errorMappingOverrides, - cancellationToken) - : _httpClientHelper.PatchAsync( - GetTwinUri(deviceId), - twin, - etag, - etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, - errorMappingOverrides, - cancellationToken); - } - - private Task UpdateTwinInternalAsync(string deviceId, string moduleId, Twin twin, string etag, bool isReplace, CancellationToken cancellationToken) - { - Logging.Enter(this, $"Replacing device twin on device: {deviceId} - module: {moduleId} - is replace: {isReplace}", nameof(UpdateTwinAsync)); - try - { - EnsureInstanceNotClosed(); - - if (twin != null) - { - twin.DeviceId = deviceId; - twin.ModuleId = moduleId; - } - - ValidateTwinId(twin); - - if (string.IsNullOrEmpty(etag)) - { - throw new ArgumentNullException(nameof(etag)); - } - - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - { - HttpStatusCode.NotFound, - async responseMessage => new ModuleNotFoundException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), - (Exception)null) - } - }; - - return isReplace - ? _httpClientHelper.PutAsync( - GetModuleTwinRequestUri(deviceId, moduleId), - twin, - etag, - etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, - errorMappingOverrides, - cancellationToken) - : _httpClientHelper.PatchAsync( - GetModuleTwinRequestUri(deviceId, moduleId), - twin, - etag, - etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, - errorMappingOverrides, - cancellationToken); - } - catch (Exception ex) - { - Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); - throw; - } - finally - { - Logging.Exit(this, $"Replacing device twin on device: {deviceId} - module: {moduleId} - is replace: {isReplace}", nameof(UpdateTwinAsync)); - } - } - - public override Task UpdateTwins2Async(IEnumerable twins) - { - return UpdateTwins2Async(twins, false, CancellationToken.None); - } - - public override Task UpdateTwins2Async(IEnumerable twins, CancellationToken cancellationToken) - { - return UpdateTwins2Async(twins, false, cancellationToken); - } - - public override Task UpdateTwins2Async(IEnumerable twins, bool forceUpdate) - { - return UpdateTwins2Async(twins, forceUpdate, CancellationToken.None); - } - - public override Task UpdateTwins2Async(IEnumerable twins, bool forceUpdate, CancellationToken cancellationToken) - { - return BulkDeviceOperationsAsync( - GenerateExportImportDeviceListForTwinBulkOperations(twins, forceUpdate ? ImportMode.UpdateTwin : ImportMode.UpdateTwinIfMatchETag), - ClientApiVersionHelper.ApiVersionQueryString, - cancellationToken); - } - - private Task RemoveDeviceAsync(string deviceId, IETagHolder eTagHolder, CancellationToken cancellationToken) - { - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.NotFound, - async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return new DeviceNotFoundException(responseContent, (Exception) null); - } - }, - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - }; - - return _httpClientHelper.DeleteAsync(GetRequestUri(deviceId), eTagHolder, errorMappingOverrides, null, cancellationToken); - } - - private Task RemoveDeviceModuleAsync(string deviceId, string moduleId, IETagHolder eTagHolder, CancellationToken cancellationToken) - { - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.NotFound, - async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return new DeviceNotFoundException(responseContent, (Exception) null); - } - }, - { - HttpStatusCode.PreconditionFailed, - async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - }; - - return _httpClientHelper.DeleteAsync(GetModulesRequestUri(deviceId, moduleId), eTagHolder, errorMappingOverrides, null, cancellationToken); - } - - private static Uri GetRequestUri(string deviceId) - { - deviceId = WebUtility.UrlEncode(deviceId); - return new Uri(RequestUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetModulesRequestUri(string deviceId, string moduleId) - { - deviceId = WebUtility.UrlEncode(deviceId); - moduleId = WebUtility.UrlEncode(moduleId); - return new Uri(ModulesRequestUriFormat.FormatInvariant(deviceId, moduleId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetModulesOnDeviceRequestUri(string deviceId) - { - deviceId = WebUtility.UrlEncode(deviceId); - return new Uri(ModulesOnDeviceRequestUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetModuleTwinRequestUri(string deviceId, string moduleId) - { - deviceId = WebUtility.UrlEncode(deviceId); - moduleId = WebUtility.UrlEncode(moduleId); - return new Uri(ModuleTwinUriFormat.FormatInvariant(deviceId, moduleId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetConfigurationRequestUri(string configurationId) - { - configurationId = WebUtility.UrlEncode(configurationId); - return new Uri(ConfigurationRequestUriFormat.FormatInvariant(configurationId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetConfigurationsRequestUri(int maxCount) - { - return new Uri(ConfigurationsRequestUriFormat.FormatInvariant(maxCount, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetApplyConfigurationOnDeviceRequestUri(string deviceId) - { - return new Uri(ApplyConfigurationOnDeviceUriFormat.FormatInvariant(deviceId), UriKind.Relative); - } - - private static Uri GetBulkRequestUri(string apiVersionQueryString) - { - return new Uri(RequestUriFormat.FormatInvariant(string.Empty, apiVersionQueryString), UriKind.Relative); - } - - private static Uri GetJobUri(string jobId, string apiVersion = ClientApiVersionHelper.ApiVersionQueryString) - { - return new Uri(JobsUriFormat.FormatInvariant(jobId, apiVersion), UriKind.Relative); - } - - private static Uri GetDevicesRequestUri(int maxCount) - { - return new Uri(DevicesRequestUriFormat.FormatInvariant(maxCount, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri QueryDevicesRequestUri() - { - return new Uri(DevicesQueryUriFormat, UriKind.Relative); - } - - private static Uri GetAdminUri(string operation) - { - return new Uri(AdminUriFormat.FormatInvariant(operation, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static Uri GetStatisticsUri() - { - return new Uri(StatisticsUriFormat, UriKind.Relative); - } - - private static Uri GetTwinUri(string deviceId) - { - deviceId = WebUtility.UrlEncode(deviceId); - return new Uri(TwinUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - - private static void ValidateDeviceId(Device device) - { - if (device == null) - { - throw new ArgumentNullException(nameof(device)); - } - - if (string.IsNullOrWhiteSpace(device.Id)) - { - throw new ArgumentException("device.Id"); - } - - if (!s_deviceIdRegex.IsMatch(device.Id)) - { - throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(device.Id)); - } - } - - private static void ValidateTwinId(Twin twin) - { - if (twin == null) - { - throw new ArgumentNullException(nameof(twin)); - } - - if (string.IsNullOrWhiteSpace(twin.DeviceId)) - { - throw new ArgumentException("twin.DeviceId"); - } - - if (!s_deviceIdRegex.IsMatch(twin.DeviceId)) - { - throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(twin.DeviceId)); - } - } - - private static void ValidateModuleId(Module module) - { - if (module == null) - { - throw new ArgumentNullException(nameof(module)); - } - - if (string.IsNullOrWhiteSpace(module.DeviceId)) - { - throw new ArgumentException("module.Id"); - } - - if (string.IsNullOrWhiteSpace(module.Id)) - { - throw new ArgumentException("module.ModuleId"); - } - - if (!s_deviceIdRegex.IsMatch(module.DeviceId)) - { - throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(module.DeviceId)); - } - - if (!s_deviceIdRegex.IsMatch(module.Id)) - { - throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(module.Id)); - } - } - - private static void ValidateDeviceAuthentication(AuthenticationMechanism authentication, string deviceId) - { - if (authentication != null) - { - // Both symmetric keys and X.509 cert thumbprints cannot be specified for the same device - bool symmetricKeyIsSet = !authentication.SymmetricKey?.IsEmpty() ?? false; - bool x509ThumbprintIsSet = !authentication.X509Thumbprint?.IsEmpty() ?? false; - - if (symmetricKeyIsSet && x509ThumbprintIsSet) - { - throw new ArgumentException(ApiResources.DeviceAuthenticationInvalid.FormatInvariant(deviceId ?? string.Empty)); - } - - // Validate X.509 thumbprints or SymmetricKeys since we should not have both at the same time - if (x509ThumbprintIsSet) - { - authentication.X509Thumbprint.IsValid(true); - } - else if (symmetricKeyIsSet) - { - authentication.SymmetricKey.IsValid(true); - } - } - } - - private void EnsureInstanceNotClosed() - { - if (_httpClientHelper == null) - { - throw new ObjectDisposedException("RegistryManager", ApiResources.RegistryManagerInstanceAlreadyClosed); - } - } - - private async Task ExecuteQueryAsync(string sqlQueryString, int? pageSize, string continuationToken, CancellationToken cancellationToken) - { - EnsureInstanceNotClosed(); - - if (string.IsNullOrWhiteSpace(sqlQueryString)) - { - throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrEmpty, nameof(sqlQueryString))); - } - - var customHeaders = new Dictionary(); - if (!string.IsNullOrWhiteSpace(continuationToken)) - { - customHeaders.Add(ContinuationTokenHeader, continuationToken); - } - - if (pageSize != null) - { - customHeaders.Add(PageSizeHeader, pageSize.ToString()); - } - - HttpResponseMessage response = await _httpClientHelper - .PostAsync( - QueryDevicesRequestUri(), - new QuerySpecification { Sql = sqlQueryString }, - null, - customHeaders, - new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }, - null, - cancellationToken) - .ConfigureAwait(false); - - return await QueryResult.FromHttpResponseAsync(response).ConfigureAwait(false); - } - - private static void NormalizeExportImportDevice(ExportImportDevice device) - { - // auto generate keys if not specified - if (device.Authentication == null) - { - device.Authentication = new AuthenticationMechanism(); - } - - NormalizeAuthenticationInfo(device.Authentication); - } - - private static void NormalizeDevice(Device device) - { - // auto generate keys if not specified - if (device.Authentication == null) - { - device.Authentication = new AuthenticationMechanism(); - } - - NormalizeAuthenticationInfo(device.Authentication); - } - - private static void NormalizeAuthenticationInfo(AuthenticationMechanism authenticationInfo) - { - //to make it backward compatible we set the type according to the values - //we don't set CA type - that has to be explicit - if (authenticationInfo.SymmetricKey != null && !authenticationInfo.SymmetricKey.IsEmpty()) - { - authenticationInfo.Type = AuthenticationType.Sas; - } - - if (authenticationInfo.X509Thumbprint != null && !authenticationInfo.X509Thumbprint.IsEmpty()) - { - authenticationInfo.Type = AuthenticationType.SelfSigned; - } - } - } -} diff --git a/iothub/service/src/JobClient/HttpJobClient.cs b/iothub/service/src/JobClient/HttpJobClient.cs deleted file mode 100644 index 88a1d2e0ab..0000000000 --- a/iothub/service/src/JobClient/HttpJobClient.cs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Common; -using Microsoft.Azure.Devices.Common.Exceptions; -using Microsoft.Azure.Devices.Shared; - -namespace Microsoft.Azure.Devices -{ - internal class HttpJobClient : JobClient - { - private const string JobsUriFormat = "/jobs/v2/{0}?{1}"; - private const string JobsQueryFormat = "/jobs/v2/query?{0}"; - private const string CancelJobUriFormat = "/jobs/v2/{0}/cancel?{1}"; - - private const string ContinuationTokenHeader = "x-ms-continuation"; - private const string PageSizeHeader = "x-ms-max-item-count"; - - private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); - - private IHttpClientHelper _httpClientHelper; - - internal HttpJobClient(IotHubConnectionProperties connectionProperties, HttpTransportSettings transportSettings) - { - _httpClientHelper = new HttpClientHelper( - connectionProperties.HttpsEndpoint, - connectionProperties, - ExceptionHandlingHelper.GetDefaultErrorMapping(), - s_defaultOperationTimeout, - transportSettings.Proxy, - transportSettings.ConnectionLeaseTimeoutMilliseconds); - } - - // internal test helper - internal HttpJobClient(IHttpClientHelper httpClientHelper) - { - _httpClientHelper = httpClientHelper ?? throw new ArgumentNullException(nameof(httpClientHelper)); - } - - public override Task OpenAsync() - { - return TaskHelpers.CompletedTask; - } - - public override Task CloseAsync() - { - return TaskHelpers.CompletedTask; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - if (_httpClientHelper != null) - { - _httpClientHelper.Dispose(); - _httpClientHelper = null; - } - } - } - - public override Task GetJobAsync(string jobId) - { - return GetJobAsync(jobId, CancellationToken.None); - } - - public override IQuery CreateQuery() - { - return CreateQuery(null, null, null); - } - - public override IQuery CreateQuery(int? pageSize) - { - return CreateQuery(null, null, pageSize); - } - - public override IQuery CreateQuery(JobType? jobType, JobStatus? jobStatus) - { - return CreateQuery(jobType, jobStatus, null); - } - - public override IQuery CreateQuery(JobType? jobType, JobStatus? jobStatus, int? pageSize) - { - return new Query((token) => GetJobsAsync(jobType, jobStatus, pageSize, token, CancellationToken.None)); - } - - public override Task CancelJobAsync(string jobId) - { - return CancelJobAsync(jobId, CancellationToken.None); - } - - public override Task GetJobAsync(string jobId, CancellationToken cancellationToken) - { - Logging.Enter(this, jobId, nameof(GetJobAsync)); - - try - { - EnsureInstanceNotClosed(); - - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.NotFound, - responseMessage => Task.FromResult((Exception) new JobNotFoundException(jobId)) - } - }; - - return _httpClientHelper.GetAsync( - GetJobUri(jobId), - errorMappingOverrides, - null, - cancellationToken); - } - finally - { - Logging.Exit(this, jobId, nameof(GetJobAsync)); - } - } - - public override Task CancelJobAsync(string jobId, CancellationToken cancellationToken) - { - Logging.Enter(this, jobId, nameof(CancelJobAsync)); - - try - { - EnsureInstanceNotClosed(); - - return _httpClientHelper.PostAsync( - new Uri(CancelJobUriFormat.FormatInvariant(jobId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative), - null, - null, - null, - cancellationToken); - } - finally - { - Logging.Exit(this, jobId, nameof(CancelJobAsync)); - } - } - - /// - public override Task ScheduleDeviceMethodAsync( - string jobId, - string queryCondition, - CloudToDeviceMethod methodCall, - DateTime startTimeUtc, - long maxExecutionTimeInSeconds) - { - return ScheduleDeviceMethodAsync(jobId, queryCondition, methodCall, startTimeUtc, maxExecutionTimeInSeconds, CancellationToken.None); - } - - /// - public override Task ScheduleDeviceMethodAsync( - string jobId, - string queryCondition, - CloudToDeviceMethod cloudToDeviceMethod, - DateTime startTimeUtc, - long maxExecutionTimeInSeconds, - CancellationToken cancellationToken) - { - EnsureInstanceNotClosed(); - - var jobRequest = new JobRequest - { - JobId = jobId, - JobType = JobType.ScheduleDeviceMethod, - CloudToDeviceMethod = cloudToDeviceMethod, - QueryCondition = queryCondition, - StartTime = startTimeUtc, - MaxExecutionTimeInSeconds = maxExecutionTimeInSeconds - }; - - return CreateJobAsync(jobRequest, cancellationToken); - } - - public override Task ScheduleTwinUpdateAsync( - string jobId, - string queryCondition, - Twin twin, - DateTime startTimeUtc, - long maxExecutionTimeInSeconds) - { - return ScheduleTwinUpdateAsync(jobId, queryCondition, twin, startTimeUtc, maxExecutionTimeInSeconds, CancellationToken.None); - } - - public override Task ScheduleTwinUpdateAsync( - string jobId, - string queryCondition, - Twin twin, - DateTime startTimeUtc, - long maxExecutionTimeInSeconds, - CancellationToken cancellationToken) - { - EnsureInstanceNotClosed(); - - var jobRequest = new JobRequest - { - JobId = jobId, - JobType = JobType.ScheduleUpdateTwin, - UpdateTwin = twin, - QueryCondition = queryCondition, - StartTime = startTimeUtc, - MaxExecutionTimeInSeconds = maxExecutionTimeInSeconds - }; - - return CreateJobAsync(jobRequest, cancellationToken); - } - - private Task CreateJobAsync(JobRequest jobRequest, CancellationToken cancellationToken) - { - Logging.Enter(this, $"jobId=[{jobRequest?.JobId}], jobType=[{jobRequest?.JobType}]", nameof(CreateJobAsync)); - - try - { - var errorMappingOverrides = new Dictionary>> - { - { - HttpStatusCode.PreconditionFailed, - async (responseMessage) => - new PreconditionFailedException( - await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) - }, - { - HttpStatusCode.NotFound, async responseMessage => - { - string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); - return (Exception) new DeviceNotFoundException(responseContent, (Exception) null); - } - } - }; - - return _httpClientHelper.PutAsync( - GetJobUri(jobRequest.JobId), - jobRequest, - errorMappingOverrides, - cancellationToken); - } - finally - { - Logging.Exit(this, $"jobId=[{jobRequest?.JobId}], jobType=[{jobRequest?.JobType}]", nameof(CreateJobAsync)); - } - } - - private void EnsureInstanceNotClosed() - { - if (_httpClientHelper == null) - { - throw new ObjectDisposedException("JobClient", ApiResources.JobClientInstanceAlreadyClosed); - } - } - - private async Task GetJobsAsync(JobType? jobType, JobStatus? jobStatus, int? pageSize, string continuationToken, CancellationToken cancellationToken) - { - Logging.Enter(this, $"jobType=[{jobType}], jobStatus=[{jobStatus}], pageSize=[{pageSize}]", nameof(GetJobsAsync)); - - try - { - EnsureInstanceNotClosed(); - - var customHeaders = new Dictionary(); - if (!string.IsNullOrWhiteSpace(continuationToken)) - { - customHeaders.Add(ContinuationTokenHeader, continuationToken); - } - - if (pageSize != null) - { - customHeaders.Add(PageSizeHeader, pageSize.ToString()); - } - - HttpResponseMessage response = await _httpClientHelper.GetAsync( - BuildQueryJobUri(jobType, jobStatus), - null, - customHeaders, - cancellationToken).ConfigureAwait(false); - - return await QueryResult.FromHttpResponseAsync(response).ConfigureAwait(false); - } - finally - { - Logging.Exit(this, $"jobType=[{jobType}], jobStatus=[{jobStatus}], pageSize=[{pageSize}]", nameof(GetJobsAsync)); - } - } - - private static Uri BuildQueryJobUri(JobType? jobType, JobStatus? jobStatus) - { - var stringBuilder = new StringBuilder(JobsQueryFormat.FormatInvariant(ClientApiVersionHelper.ApiVersionQueryString)); - - if (jobType != null) - { - stringBuilder.Append("&jobType={0}".FormatInvariant(WebUtility.UrlEncode(jobType.ToString()))); - } - - if (jobStatus != null) - { - stringBuilder.Append("&jobStatus={0}".FormatInvariant(WebUtility.UrlEncode(jobStatus.ToString()))); - } - - return new Uri(stringBuilder.ToString(), UriKind.Relative); - } - - private static Uri GetJobUri(string jobId) - { - return new Uri(JobsUriFormat.FormatInvariant(jobId ?? string.Empty, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); - } - } -} diff --git a/iothub/service/src/JobClient/JobClient.cs b/iothub/service/src/JobClient/JobClient.cs index c5dca84b67..32421512b4 100644 --- a/iothub/service/src/JobClient/JobClient.cs +++ b/iothub/service/src/JobClient/JobClient.cs @@ -4,6 +4,12 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Common; +using System.Collections.Generic; +using System.Net; +using Microsoft.Azure.Devices.Common.Exceptions; +using System.Net.Http; +using System.Text; #if !NET451 @@ -17,13 +23,49 @@ namespace Microsoft.Azure.Devices /// /// Job management /// - public abstract class JobClient : IDisposable + public class JobClient : IDisposable { + private const string _jobsUriFormat = "/jobs/v2/{0}?{1}"; + private const string _jobsQueryFormat = "/jobs/v2/query?{0}"; + private const string _CancelJobUriFormat = "/jobs/v2/{0}/cancel?{1}"; + + private const string _continuationTokenHeader = "x-ms-continuation"; + private const string _pageSizeHeader = "x-ms-max-item-count"; + + private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); + + private IHttpClientHelper _httpClientHelper; + /// - /// Creates a JobClient from the Iot Hub connection string. + /// Creates an instance of , provided for unit testing purposes only. + /// Use the CreateFromConnectionString method to create an instance to use the client. + /// + public JobClient() + { + } + + // internal test helper + internal JobClient(IHttpClientHelper httpClientHelper) + { + _httpClientHelper = httpClientHelper; + } + + internal JobClient(IotHubConnectionProperties connectionProperties, HttpTransportSettings transportSettings) + { + _httpClientHelper = new HttpClientHelper( + connectionProperties.HttpsEndpoint, + connectionProperties, + ExceptionHandlingHelper.GetDefaultErrorMapping(), + s_defaultOperationTimeout, + transportSettings.Proxy, + transportSettings.ConnectionLeaseTimeoutMilliseconds); + } + + /// + /// Creates a JobClient from the IoT Hub connection string. /// For more information, see /// - /// The Iot Hub connection string. + /// The IoT Hub connection string. /// A JobClient instance. public static JobClient CreateFromConnectionString(string connectionString) { @@ -31,9 +73,9 @@ public static JobClient CreateFromConnectionString(string connectionString) } /// - /// Creates a JobClient from the Iot Hub connection string and HTTP transport settings + /// Creates a JobClient from the IoT Hub connection string and HTTP transport settings /// - /// The Iot Hub connection string. + /// The IoT Hub connection string. /// The HTTP transport settings. /// A JobClient instance. public static JobClient CreateFromConnectionString(string connectionString, HttpTransportSettings transportSettings) @@ -45,7 +87,7 @@ public static JobClient CreateFromConnectionString(string connectionString, Http TlsVersions.Instance.SetLegacyAcceptableVersions(); var iotHubConnectionString = IotHubConnectionString.Parse(connectionString); - return new HttpJobClient(iotHubConnectionString, transportSettings); + return new JobClient(iotHubConnectionString, transportSettings); } #if !NET451 @@ -76,7 +118,7 @@ public static JobClient CreateFromConnectionString(string connectionString, Http } var tokenCredentialProperties = new IotHubTokenCrendentialProperties(hostName, credential); - return new HttpJobClient(tokenCredentialProperties, transportSettings ?? new HttpTransportSettings()); + return new JobClient(tokenCredentialProperties, transportSettings ?? new HttpTransportSettings()); } /// @@ -102,7 +144,7 @@ public static JobClient CreateFromConnectionString(string connectionString, Http } var sasCredentialProperties = new IotHubSasCredentialProperties(hostName, credential); - return new HttpJobClient(sasCredentialProperties, transportSettings ?? new HttpTransportSettings()); + return new JobClient(sasCredentialProperties, transportSettings ?? new HttpTransportSettings()); } #endif @@ -110,7 +152,7 @@ public static JobClient CreateFromConnectionString(string connectionString, Http /// public void Dispose() { - this.Dispose(true); + Dispose(true); GC.SuppressFinalize(this); } @@ -118,24 +160,43 @@ public void Dispose() /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) { } + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_httpClientHelper != null) + { + _httpClientHelper.Dispose(); + _httpClientHelper = null; + } + } + } /// /// Explicitly open the JobClient instance. /// - public abstract Task OpenAsync(); + public virtual Task OpenAsync() + { + return TaskHelpers.CompletedTask; + } /// /// Closes the JobClient instance and disposes its resources. /// - public abstract Task CloseAsync(); + public virtual Task CloseAsync() + { + return TaskHelpers.CompletedTask; + } /// /// Gets the job with the specified Id. /// /// Id of the Job to retrieve /// The matching JobResponse object - public abstract Task GetJobAsync(string jobId); + public virtual Task GetJobAsync(string jobId) + { + return GetJobAsync(jobId, CancellationToken.None); + } /// /// Gets the job with the specified Id. @@ -143,20 +204,52 @@ public void Dispose() /// Id of the job to retrieve /// Task cancellation token /// The matching JobResponse object - public abstract Task GetJobAsync(string jobId, CancellationToken cancellationToken); + public virtual Task GetJobAsync(string jobId, CancellationToken cancellationToken) + { + Logging.Enter(this, jobId, nameof(GetJobAsync)); + + try + { + EnsureInstanceNotClosed(); + + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.NotFound, + responseMessage => Task.FromResult((Exception) new JobNotFoundException(jobId)) + } + }; + + return _httpClientHelper.GetAsync( + GetJobUri(jobId), + errorMappingOverrides, + null, + cancellationToken); + } + finally + { + Logging.Exit(this, jobId, nameof(GetJobAsync)); + } + } /// /// Get IQuery through which job responses for all job types and statuses are retrieved page by page /// /// IQuery - public abstract IQuery CreateQuery(); + public virtual IQuery CreateQuery() + { + return CreateQuery(null, null, null); + } /// /// Get IQuery through which job responses are retrieved page by page and specify page size /// /// Number of job responses in a page /// - public abstract IQuery CreateQuery(int? pageSize); + public virtual IQuery CreateQuery(int? pageSize) + { + return CreateQuery(null, null, pageSize); + } /// /// Get IQuery through which job responses for specified jobType and jobStatus are retrieved page by page @@ -164,7 +257,10 @@ public void Dispose() /// The job type to query. Could be null if not querying. /// The job status to query. Could be null if not querying. /// - public abstract IQuery CreateQuery(JobType? jobType, JobStatus? jobStatus); + public virtual IQuery CreateQuery(JobType? jobType, JobStatus? jobStatus) + { + return CreateQuery(jobType, jobStatus, null); + } /// /// Get IQuery through which job responses for specified jobType and jobStatus are retrieved page by page, @@ -174,20 +270,45 @@ public void Dispose() /// The job status to query. Could be null if not querying. /// Number of job responses in a page /// - public abstract IQuery CreateQuery(JobType? jobType, JobStatus? jobStatus, int? pageSize); + public virtual IQuery CreateQuery(JobType? jobType, JobStatus? jobStatus, int? pageSize) + { + return new Query((token) => GetJobsAsync(jobType, jobStatus, pageSize, token, CancellationToken.None)); + } /// /// Cancels/Deletes the job with the specified Id. /// /// Id of the Job to cancel - public abstract Task CancelJobAsync(string jobId); + public virtual Task CancelJobAsync(string jobId) + { + return CancelJobAsync(jobId, CancellationToken.None); + } /// /// Cancels/Deletes the job with the specified Id. /// /// Id of the job to cancel /// Task cancellation token - public abstract Task CancelJobAsync(string jobId, CancellationToken cancellationToken); + public virtual Task CancelJobAsync(string jobId, CancellationToken cancellationToken) + { + Logging.Enter(this, jobId, nameof(CancelJobAsync)); + + try + { + EnsureInstanceNotClosed(); + + return _httpClientHelper.PostAsync( + new Uri(_CancelJobUriFormat.FormatInvariant(jobId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative), + null, + null, + null, + cancellationToken); + } + finally + { + Logging.Exit(this, jobId, nameof(CancelJobAsync)); + } + } /// /// Creates a new Job to run a device method on one or multiple devices @@ -198,7 +319,10 @@ public void Dispose() /// Date time in Utc to start the job /// Max execution time in seconds, i.e., ttl duration the job can run /// A JobResponse object - public abstract Task ScheduleDeviceMethodAsync(string jobId, string queryCondition, CloudToDeviceMethod cloudToDeviceMethod, DateTime startTimeUtc, long maxExecutionTimeInSeconds); + public virtual Task ScheduleDeviceMethodAsync(string jobId, string queryCondition, CloudToDeviceMethod cloudToDeviceMethod, DateTime startTimeUtc, long maxExecutionTimeInSeconds) + { + return ScheduleDeviceMethodAsync(jobId, queryCondition, cloudToDeviceMethod, startTimeUtc, maxExecutionTimeInSeconds, CancellationToken.None); + } /// /// Creates a new Job to run a device method on one or multiple devices @@ -210,7 +334,22 @@ public void Dispose() /// Max execution time in seconds, i.e., ttl duration the job can run /// Task cancellation token /// A JobResponse object - public abstract Task ScheduleDeviceMethodAsync(string jobId, string queryCondition, CloudToDeviceMethod cloudToDeviceMethod, DateTime startTimeUtc, long maxExecutionTimeInSeconds, CancellationToken cancellationToken); + public virtual Task ScheduleDeviceMethodAsync(string jobId, string queryCondition, CloudToDeviceMethod cloudToDeviceMethod, DateTime startTimeUtc, long maxExecutionTimeInSeconds, CancellationToken cancellationToken) + { + EnsureInstanceNotClosed(); + + var jobRequest = new JobRequest + { + JobId = jobId, + JobType = JobType.ScheduleDeviceMethod, + CloudToDeviceMethod = cloudToDeviceMethod, + QueryCondition = queryCondition, + StartTime = startTimeUtc, + MaxExecutionTimeInSeconds = maxExecutionTimeInSeconds + }; + + return CreateJobAsync(jobRequest, cancellationToken); + } /// /// Creates a new Job to update twin tags and desired properties on one or multiple devices @@ -221,7 +360,10 @@ public void Dispose() /// Date time in Utc to start the job /// Max execution time in seconds, i.e., ttl duration the job can run /// A JobResponse object - public abstract Task ScheduleTwinUpdateAsync(string jobId, string queryCondition, Twin twin, DateTime startTimeUtc, long maxExecutionTimeInSeconds); + public virtual Task ScheduleTwinUpdateAsync(string jobId, string queryCondition, Twin twin, DateTime startTimeUtc, long maxExecutionTimeInSeconds) + { + return ScheduleTwinUpdateAsync(jobId, queryCondition, twin, startTimeUtc, maxExecutionTimeInSeconds, CancellationToken.None); + } /// /// Creates a new Job to update twin tags and desired properties on one or multiple devices @@ -233,6 +375,119 @@ public void Dispose() /// Max execution time in seconds, i.e., ttl duration the job can run /// Task cancellation token /// A JobResponse object - public abstract Task ScheduleTwinUpdateAsync(string jobId, string queryCondition, Twin twin, DateTime startTimeUtc, long maxExecutionTimeInSeconds, CancellationToken cancellationToken); + public virtual Task ScheduleTwinUpdateAsync(string jobId, string queryCondition, Twin twin, DateTime startTimeUtc, long maxExecutionTimeInSeconds, CancellationToken cancellationToken) + { + EnsureInstanceNotClosed(); + + var jobRequest = new JobRequest + { + JobId = jobId, + JobType = JobType.ScheduleUpdateTwin, + UpdateTwin = twin, + QueryCondition = queryCondition, + StartTime = startTimeUtc, + MaxExecutionTimeInSeconds = maxExecutionTimeInSeconds + }; + + return CreateJobAsync(jobRequest, cancellationToken); + } + + private void EnsureInstanceNotClosed() + { + if (_httpClientHelper == null) + { + throw new ObjectDisposedException("JobClient", ApiResources.JobClientInstanceAlreadyClosed); + } + } + + private static Uri GetJobUri(string jobId) + { + return new Uri(_jobsUriFormat.FormatInvariant(jobId ?? string.Empty, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private Task CreateJobAsync(JobRequest jobRequest, CancellationToken cancellationToken) + { + Logging.Enter(this, $"jobId=[{jobRequest?.JobId}], jobType=[{jobRequest?.JobType}]", nameof(CreateJobAsync)); + + try + { + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.PreconditionFailed, + async (responseMessage) => + new PreconditionFailedException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + { + HttpStatusCode.NotFound, async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return (Exception) new DeviceNotFoundException(responseContent, (Exception) null); + } + } + }; + + return _httpClientHelper.PutAsync( + GetJobUri(jobRequest.JobId), + jobRequest, + errorMappingOverrides, + cancellationToken); + } + finally + { + Logging.Exit(this, $"jobId=[{jobRequest?.JobId}], jobType=[{jobRequest?.JobType}]", nameof(CreateJobAsync)); + } + } + + private static Uri BuildQueryJobUri(JobType? jobType, JobStatus? jobStatus) + { + var stringBuilder = new StringBuilder(_jobsQueryFormat.FormatInvariant(ClientApiVersionHelper.ApiVersionQueryString)); + + if (jobType != null) + { + stringBuilder.Append("&jobType={0}".FormatInvariant(WebUtility.UrlEncode(jobType.ToString()))); + } + + if (jobStatus != null) + { + stringBuilder.Append("&jobStatus={0}".FormatInvariant(WebUtility.UrlEncode(jobStatus.ToString()))); + } + + return new Uri(stringBuilder.ToString(), UriKind.Relative); + } + + private async Task GetJobsAsync(JobType? jobType, JobStatus? jobStatus, int? pageSize, string continuationToken, CancellationToken cancellationToken) + { + Logging.Enter(this, $"jobType=[{jobType}], jobStatus=[{jobStatus}], pageSize=[{pageSize}]", nameof(GetJobsAsync)); + + try + { + EnsureInstanceNotClosed(); + + var customHeaders = new Dictionary(); + if (!string.IsNullOrWhiteSpace(continuationToken)) + { + customHeaders.Add(_continuationTokenHeader, continuationToken); + } + + if (pageSize != null) + { + customHeaders.Add(_pageSizeHeader, pageSize.ToString()); + } + + HttpResponseMessage response = await _httpClientHelper.GetAsync( + BuildQueryJobUri(jobType, jobStatus), + null, + customHeaders, + cancellationToken).ConfigureAwait(false); + + return await QueryResult.FromHttpResponseAsync(response).ConfigureAwait(false); + } + finally + { + Logging.Exit(this, $"jobType=[{jobType}], jobStatus=[{jobStatus}], pageSize=[{pageSize}]", nameof(GetJobsAsync)); + } + } } } diff --git a/iothub/service/src/RegistryManager.cs b/iothub/service/src/RegistryManager.cs index ef7ce5c51b..ed6882f3d1 100644 --- a/iothub/service/src/RegistryManager.cs +++ b/iothub/service/src/RegistryManager.cs @@ -3,9 +3,17 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Common; +using Microsoft.Azure.Devices.Common.Exceptions; using Microsoft.Azure.Devices.Shared; +using Newtonsoft.Json; #if !NET451 @@ -24,8 +32,70 @@ namespace Microsoft.Azure.Devices "Naming", "CA1716:Identifiers should not match keywords", Justification = "Cannot change parameter names as it is considered a breaking change.")] - public abstract class RegistryManager : IDisposable + public class RegistryManager : IDisposable { + private const string _adminUriFormat = "/$admin/{0}?{1}"; + private const string _requestUriFormat = "/devices/{0}?{1}"; + private const string _jobsUriFormat = "/jobs{0}?{1}"; + private const string _statisticsUriFormat = "/statistics/devices?" + ClientApiVersionHelper.ApiVersionQueryString; + private const string _devicesRequestUriFormat = "/devices/?top={0}&{1}"; + private const string _devicesQueryUriFormat = "/devices/query?" + ClientApiVersionHelper.ApiVersionQueryString; + private const string _wildcardEtag = "*"; + + private const string _continuationTokenHeader = "x-ms-continuation"; + private const string _pageSizeHeader = "x-ms-max-item-count"; + + private const string _twinUriFormat = "/twins/{0}?{1}"; + + private const string _modulesRequestUriFormat = "/devices/{0}/modules/{1}?{2}"; + private const string _modulesOnDeviceRequestUriFormat = "/devices/{0}/modules?{1}"; + private const string _moduleTwinUriFormat = "/twins/{0}/modules/{1}?{2}"; + + private const string _configurationRequestUriFormat = "/configurations/{0}?{1}"; + private const string _configurationsRequestUriFormat = "/configurations/?top={0}&{1}"; + + private const string _applyConfigurationOnDeviceUriFormat = "/devices/{0}/applyConfigurationContent?" + ClientApiVersionHelper.ApiVersionQueryString; + + private static readonly TimeSpan s_regexTimeoutMilliseconds = TimeSpan.FromMilliseconds(500); + + private static readonly Regex s_deviceIdRegex = new Regex( + @"^[A-Za-z0-9\-:.+%_#*?!(),=@;$']{1,128}$", + RegexOptions.Compiled | RegexOptions.IgnoreCase, + s_regexTimeoutMilliseconds); + + private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); + private static readonly TimeSpan s_defaultGetDevicesOperationTimeout = TimeSpan.FromSeconds(120); + + private readonly string _iotHubName; + private IHttpClientHelper _httpClientHelper; + + /// + /// Creates an instance of , provided for unit testing purposes only. + /// Use the CreateFromConnectionString method to create an instance to use the client. + /// + public RegistryManager() + { + } + + internal RegistryManager(IotHubConnectionProperties connectionProperties, HttpTransportSettings transportSettings) + { + _iotHubName = connectionProperties.IotHubName; + _httpClientHelper = new HttpClientHelper( + connectionProperties.HttpsEndpoint, + connectionProperties, + ExceptionHandlingHelper.GetDefaultErrorMapping(), + s_defaultOperationTimeout, + transportSettings.Proxy, + transportSettings.ConnectionLeaseTimeoutMilliseconds); + } + + // internal test helper + internal RegistryManager(string iotHubName, IHttpClientHelper httpClientHelper) + { + _iotHubName = iotHubName; + _httpClientHelper = httpClientHelper ?? throw new ArgumentNullException(nameof(httpClientHelper)); + } + /// /// Creates a RegistryManager from the IoT Hub connection string. /// @@ -51,7 +121,7 @@ public static RegistryManager CreateFromConnectionString(string connectionString TlsVersions.Instance.SetLegacyAcceptableVersions(); var iotHubConnectionString = IotHubConnectionString.Parse(connectionString); - return new HttpRegistryManager(iotHubConnectionString, transportSettings); + return new RegistryManager(iotHubConnectionString, transportSettings); } #if !NET451 @@ -82,7 +152,7 @@ public static RegistryManager CreateFromConnectionString(string connectionString } var tokenCredentialProperties = new IotHubTokenCrendentialProperties(hostName, credential); - return new HttpRegistryManager(tokenCredentialProperties, transportSettings ?? new HttpTransportSettings()); + return new RegistryManager(tokenCredentialProperties, transportSettings ?? new HttpTransportSettings()); } /// @@ -108,7 +178,7 @@ public static RegistryManager CreateFromConnectionString(string connectionString } var sasCredentialProperties = new IotHubSasCredentialProperties(hostName, credential); - return new HttpRegistryManager(sasCredentialProperties, transportSettings ?? new HttpTransportSettings()); + return new RegistryManager(sasCredentialProperties, transportSettings ?? new HttpTransportSettings()); } #endif @@ -124,24 +194,40 @@ public void Dispose() /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) { } + protected virtual void Dispose(bool disposing) + { + if (disposing && _httpClientHelper != null) + { + _httpClientHelper.Dispose(); + _httpClientHelper = null; + } + } /// /// Explicitly open the RegistryManager instance. /// - public abstract Task OpenAsync(); + public virtual Task OpenAsync() + { + return TaskHelpers.CompletedTask; + } /// /// Closes the RegistryManager instance and disposes its resources. /// - public abstract Task CloseAsync(); + public virtual Task CloseAsync() + { + return TaskHelpers.CompletedTask; + } /// /// Register a new device with the system /// /// The Device object being registered. /// The Device object with the generated keys and ETags. - public abstract Task AddDeviceAsync(Device device); + public virtual Task AddDeviceAsync(Device device) + { + return AddDeviceAsync(device, CancellationToken.None); + } /// /// Register a new device with the system @@ -149,14 +235,55 @@ public void Dispose() /// The Device object being registered. /// The token which allows the operation to be canceled. /// The Device object with the generated keys and ETags. - public abstract Task AddDeviceAsync(Device device, CancellationToken cancellationToken); + public virtual Task AddDeviceAsync(Device device, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Adding device: {device?.Id}", nameof(AddDeviceAsync)); + + try + { + EnsureInstanceNotClosed(); + + ValidateDeviceId(device); + + if (!string.IsNullOrEmpty(device.ETag)) + { + throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); + } + + ValidateDeviceAuthentication(device.Authentication, device.Id); + + NormalizeDevice(device); + + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + } + }; + + return _httpClientHelper.PutAsync(GetRequestUri(device.Id), device, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(AddDeviceAsync)} threw an exception: {ex}", nameof(AddDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Adding device: {device?.Id}", nameof(AddDeviceAsync)); + } + } /// /// Register a new module with device in the system /// /// The Module object being registered. /// The Module object with the generated keys and ETags. - public abstract Task AddModuleAsync(Module module); + public virtual Task AddModuleAsync(Module module) + { + return AddModuleAsync(module, CancellationToken.None); + } /// /// Register a new module with device in the system @@ -164,7 +291,60 @@ public void Dispose() /// The Module object being registered. /// The token which allows the operation to be canceled. /// The Module object with the generated keys and ETags. - public abstract Task AddModuleAsync(Module module, CancellationToken cancellationToken); + public virtual Task AddModuleAsync(Module module, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Adding module: {module?.Id}", nameof(AddModuleAsync)); + + try + { + EnsureInstanceNotClosed(); + + ValidateModuleId(module); + + if (!string.IsNullOrEmpty(module.ETag)) + { + throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); + } + + ValidateDeviceAuthentication(module.Authentication, module.DeviceId); + + // auto generate keys if not specified + if (module.Authentication == null) + { + module.Authentication = new AuthenticationMechanism(); + } + + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + { + HttpStatusCode.Conflict, + async responseMessage => new ModuleAlreadyExistsException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + { + HttpStatusCode.RequestEntityTooLarge, + async responseMessage => new TooManyModulesOnDeviceException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + } + }; + + return _httpClientHelper.PutAsync(GetModulesRequestUri(module.DeviceId, module.Id), module, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(AddModuleAsync)} threw an exception: {ex}", nameof(AddModuleAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Adding module: {module?.Id}", nameof(AddModuleAsync)); + } + } /// /// Adds a Device with Twin information @@ -172,7 +352,10 @@ public void Dispose() /// The device to add. /// The twin information for the device being added. /// The result of the add operation. - public abstract Task AddDeviceWithTwinAsync(Device device, Twin twin); + public virtual Task AddDeviceWithTwinAsync(Device device, Twin twin) + { + return AddDeviceWithTwinAsync(device, twin, CancellationToken.None); + } /// /// Adds a Device with Twin information @@ -181,7 +364,46 @@ public void Dispose() /// The twin information for the device being added. /// A cancellation token to cancel the operation. /// The result of the add operation. - public abstract Task AddDeviceWithTwinAsync(Device device, Twin twin, CancellationToken cancellationToken); + public virtual Task AddDeviceWithTwinAsync(Device device, Twin twin, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Adding device with twin: {device?.Id}", nameof(AddDeviceWithTwinAsync)); + + try + { + ValidateDeviceId(device); + if (!string.IsNullOrWhiteSpace(device.ETag)) + { + throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); + } + var exportImportDeviceList = new List(1); + + var exportImportDevice = new ExportImportDevice(device, ImportMode.Create) + { + Tags = twin?.Tags, + Properties = new ExportImportDevice.PropertyContainer + { + DesiredProperties = twin?.Properties.Desired, + ReportedProperties = twin?.Properties.Reported, + } + }; + + exportImportDeviceList.Add(exportImportDevice); + + return BulkDeviceOperationsAsync( + exportImportDeviceList, + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(AddDeviceWithTwinAsync)} threw an exception: {ex}", nameof(AddDeviceWithTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Adding device with twin: {device?.Id}", nameof(AddDeviceWithTwinAsync)); + } + } /// /// Register a list of new devices with the system @@ -189,7 +411,10 @@ public void Dispose() /// The Device objects being registered. /// Returns a string array of error messages. [Obsolete("Use AddDevices2Async")] - public abstract Task AddDevicesAsync(IEnumerable devices); + public virtual Task AddDevicesAsync(IEnumerable devices) + { + return AddDevicesAsync(devices, CancellationToken.None); + } /// /// Register a list of new devices with the system @@ -198,14 +423,37 @@ public void Dispose() /// The token which allows the operation to be canceled. /// Returns a string array of error messages. [Obsolete("Use AddDevices2Async")] - public abstract Task AddDevicesAsync(IEnumerable devices, CancellationToken cancellationToken); + public virtual Task AddDevicesAsync(IEnumerable devices, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Adding {devices?.Count()} devices", nameof(AddDevicesAsync)); + + try + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForBulkOperations(devices, ImportMode.Create), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(AddDevicesAsync)} threw an exception: {ex}", nameof(AddDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Adding {devices?.Count()} devices", nameof(AddDevicesAsync)); + } + } /// /// Register a list of new devices with the system /// /// The Device objects being registered. /// Returns a BulkRegistryOperationResult object. - public abstract Task AddDevices2Async(IEnumerable devices); + public virtual Task AddDevices2Async(IEnumerable devices) + { + return AddDevices2Async(devices, CancellationToken.None); + } /// /// Register a list of new devices with the system @@ -213,14 +461,37 @@ public void Dispose() /// The Device objects being registered. /// The token which allows the operation to be canceled. /// Returns a BulkRegistryOperationResult object. - public abstract Task AddDevices2Async(IEnumerable devices, CancellationToken cancellationToken); + public virtual Task AddDevices2Async(IEnumerable devices, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Adding {devices?.Count()} devices", nameof(AddDevices2Async)); + + try + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForBulkOperations(devices, ImportMode.Create), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(AddDevices2Async)} threw an exception: {ex}", nameof(AddDevices2Async)); + throw; + } + finally + { + Logging.Exit(this, $"Adding {devices?.Count()} devices", nameof(AddDevices2Async)); + } + } /// /// Update the mutable fields of the device registration /// /// The Device object with updated fields. /// The Device object with updated ETag. - public abstract Task UpdateDeviceAsync(Device device); + public virtual Task UpdateDeviceAsync(Device device) + { + return UpdateDeviceAsync(device, CancellationToken.None); + } /// /// Update the mutable fields of the device registration @@ -228,7 +499,10 @@ public void Dispose() /// The Device object with updated fields. /// Forces the device object to be replaced without regard for an ETag match. /// The Device object with updated ETag. - public abstract Task UpdateDeviceAsync(Device device, bool forceUpdate); + public virtual Task UpdateDeviceAsync(Device device, bool forceUpdate) + { + return UpdateDeviceAsync(device, forceUpdate, CancellationToken.None); + } /// /// Update the mutable fields of the device registration @@ -236,7 +510,10 @@ public void Dispose() /// The Device object with updated fields. /// The token which allows the operation to be canceled. /// The Device object with updated ETag. - public abstract Task UpdateDeviceAsync(Device device, CancellationToken cancellationToken); + public virtual Task UpdateDeviceAsync(Device device, CancellationToken cancellationToken) + { + return UpdateDeviceAsync(device, false, cancellationToken); + } /// /// Update the mutable fields of the device registration @@ -245,14 +522,60 @@ public void Dispose() /// Forces the device object to be replaced even if it was updated since it was retrieved last time. /// The token which allows the operation to be canceled. /// The Device object with updated ETags. - public abstract Task UpdateDeviceAsync(Device device, bool forceUpdate, CancellationToken cancellationToken); + public virtual Task UpdateDeviceAsync(Device device, bool forceUpdate, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating device: {device?.Id}", nameof(UpdateDeviceAsync)); + try + { + EnsureInstanceNotClosed(); + + ValidateDeviceId(device); + + if (string.IsNullOrWhiteSpace(device.ETag) && !forceUpdate) + { + throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); + } + + ValidateDeviceAuthentication(device.Authentication, device.Id); + + NormalizeDevice(device); + + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, + { + HttpStatusCode.NotFound, async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return (Exception)new DeviceNotFoundException(responseContent, (Exception)null); + } + } + }; + + PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; + + return _httpClientHelper.PutAsync(GetRequestUri(device.Id), device, operationType, errorMappingOverrides, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateDeviceAsync)} threw an exception: {ex}", nameof(UpdateDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Updating device: {device?.Id}", nameof(UpdateDeviceAsync)); + } + } /// /// Update the mutable fields of the module registration /// /// The Module object with updated fields. /// The Module object with updated ETags. - public abstract Task UpdateModuleAsync(Module module); + public virtual Task UpdateModuleAsync(Module module) + { + return UpdateModuleAsync(module, CancellationToken.None); + } /// /// Update the mutable fields of the module registration @@ -260,7 +583,10 @@ public void Dispose() /// The Module object with updated fields. /// Forces the device object to be replaced without regard for an ETag match. /// The Module object with updated ETags. - public abstract Task UpdateModuleAsync(Module module, bool forceUpdate); + public virtual Task UpdateModuleAsync(Module module, bool forceUpdate) + { + return UpdateModuleAsync(module, forceUpdate, CancellationToken.None); + } /// /// Update the mutable fields of the module registration @@ -268,7 +594,10 @@ public void Dispose() /// The Module object with updated fields. /// The token which allows the operation to be canceled. /// The Module object with updated ETags. - public abstract Task UpdateModuleAsync(Module module, CancellationToken cancellationToken); + public virtual Task UpdateModuleAsync(Module module, CancellationToken cancellationToken) + { + return UpdateModuleAsync(module, false, CancellationToken.None); + } /// /// Update the mutable fields of the module registration @@ -277,7 +606,55 @@ public void Dispose() /// Forces the module object to be replaced even if it was updated since it was retrieved last time. /// The token which allows the operation to be canceled. /// The Module object with updated ETags. - public abstract Task UpdateModuleAsync(Module module, bool forceUpdate, CancellationToken cancellationToken); + public virtual Task UpdateModuleAsync(Module module, bool forceUpdate, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating module: {module?.Id}", nameof(UpdateModuleAsync)); + + try + { + EnsureInstanceNotClosed(); + + ValidateModuleId(module); + + if (string.IsNullOrWhiteSpace(module.ETag) && !forceUpdate) + { + throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); + } + + ValidateDeviceAuthentication(module.Authentication, module.DeviceId); + + // auto generate keys if not specified + if (module.Authentication == null) + { + module.Authentication = new AuthenticationMechanism(); + } + + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, + { + HttpStatusCode.NotFound, async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return new ModuleNotFoundException(responseContent, (Exception)null); + } + } + }; + + PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; + + return _httpClientHelper.PutAsync(GetModulesRequestUri(module.DeviceId, module.Id), module, operationType, errorMappingOverrides, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateModuleAsync)} threw an exception: {ex}", nameof(UpdateModuleAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Updating module: {module?.Id}", nameof(UpdateModuleAsync)); + } + } /// /// Update a list of devices with the system @@ -285,7 +662,10 @@ public void Dispose() /// The Device objects being updated. /// Returns a string array of error messages. [Obsolete("Use UpdateDevices2Async")] - public abstract Task UpdateDevicesAsync(IEnumerable devices); + public virtual Task UpdateDevicesAsync(IEnumerable devices) + { + return UpdateDevicesAsync(devices, false, CancellationToken.None); + } /// /// Update a list of devices with the system @@ -295,14 +675,37 @@ public void Dispose() /// The token which allows the operation to be canceled. /// Returns a string array of error messages. [Obsolete("Use UpdateDevices2Async")] - public abstract Task UpdateDevicesAsync(IEnumerable devices, bool forceUpdate, CancellationToken cancellationToken); + public virtual Task UpdateDevicesAsync(IEnumerable devices, bool forceUpdate, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating multiple devices: count: {devices?.Count()}", nameof(UpdateDevicesAsync)); + + try + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForBulkOperations(devices, forceUpdate ? ImportMode.Update : ImportMode.UpdateIfMatchETag), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateDevicesAsync)} threw an exception: {ex}", nameof(UpdateDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Updating multiple devices: count: {devices?.Count()}", nameof(UpdateDevicesAsync)); + } + } /// /// Update a list of devices with the system /// /// The Device objects being updated. /// Returns a BulkRegistryOperationResult object. - public abstract Task UpdateDevices2Async(IEnumerable devices); + public virtual Task UpdateDevices2Async(IEnumerable devices) + { + return UpdateDevices2Async(devices, false, CancellationToken.None); + } /// /// Update a list of devices with the system @@ -311,40 +714,117 @@ public void Dispose() /// Forces the device object to be replaced even if it was updated since it was retrieved last time. /// The token which allows the operation to be canceled. /// Returns a BulkRegistryOperationResult object. - public abstract Task UpdateDevices2Async(IEnumerable devices, bool forceUpdate, CancellationToken cancellationToken); + public virtual Task UpdateDevices2Async(IEnumerable devices, bool forceUpdate, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating multiple devices: count: {devices?.Count()} - Force update: {forceUpdate}", nameof(UpdateDevices2Async)); + + try + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForBulkOperations(devices, forceUpdate ? ImportMode.Update : ImportMode.UpdateIfMatchETag), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateDevices2Async)} threw an exception: {ex}", nameof(UpdateDevices2Async)); + throw; + } + finally + { + Logging.Exit(this, $"Updating multiple devices: count: {devices?.Count()} - Force update: {forceUpdate}", nameof(UpdateDevices2Async)); + } + } /// /// Deletes a previously registered device from the system. /// /// The id of the device being deleted. - public abstract Task RemoveDeviceAsync(string deviceId); + public virtual Task RemoveDeviceAsync(string deviceId) + { + return RemoveDeviceAsync(deviceId, CancellationToken.None); + } /// /// Deletes a previously registered device from the system. /// /// The id of the device being deleted. /// The token which allows the operation to be canceled. - public abstract Task RemoveDeviceAsync(string deviceId, CancellationToken cancellationToken); + public virtual Task RemoveDeviceAsync(string deviceId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing device: {deviceId}", nameof(RemoveDeviceAsync)); + try + { + EnsureInstanceNotClosed(); + + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); + } + + // use wild-card ETag + var eTag = new ETagHolder { ETag = "*" }; + return RemoveDeviceAsync(deviceId, eTag, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveDeviceAsync)} threw an exception: {ex}", nameof(RemoveDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing device: {deviceId}", nameof(RemoveDeviceAsync)); + } + } /// /// Deletes a previously registered device from the system. /// /// The device being deleted. - public abstract Task RemoveDeviceAsync(Device device); + public virtual Task RemoveDeviceAsync(Device device) + { + return RemoveDeviceAsync(device, CancellationToken.None); + } /// /// Deletes a previously registered device from the system. /// /// The device being deleted. /// The token which allows the operation to be canceled. - public abstract Task RemoveDeviceAsync(Device device, CancellationToken cancellationToken); + public virtual Task RemoveDeviceAsync(Device device, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing device: {device?.Id}", nameof(RemoveDeviceAsync)); + + try + { + EnsureInstanceNotClosed(); + + ValidateDeviceId(device); + + return string.IsNullOrWhiteSpace(device.ETag) + ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice) + : RemoveDeviceAsync(device.Id, device, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveDeviceAsync)} threw an exception: {ex}", nameof(RemoveDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing device: {device?.Id}", nameof(RemoveDeviceAsync)); + } + } /// /// Deletes a previously registered module from device in the system. /// /// The id of the device being deleted. /// The id of the moduleId being deleted. - public abstract Task RemoveModuleAsync(string deviceId, string moduleId); + public virtual Task RemoveModuleAsync(string deviceId, string moduleId) + { + return RemoveModuleAsync(deviceId, moduleId, CancellationToken.None); + } /// /// Deletes a previously registered module from device in the system. @@ -352,27 +832,82 @@ public void Dispose() /// The id of the device being deleted. /// The id of the moduleId being deleted. /// The token which allows the operation to be canceled. - public abstract Task RemoveModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken); + public virtual Task RemoveModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing module: device Id:{deviceId} moduleId: {moduleId}", nameof(RemoveDeviceAsync)); + + try + { + EnsureInstanceNotClosed(); + + if (string.IsNullOrWhiteSpace(deviceId) || string.IsNullOrEmpty(moduleId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); + } + + // use wild-card ETag + var eTag = new ETagHolder { ETag = "*" }; + return RemoveDeviceModuleAsync(deviceId, moduleId, eTag, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveModuleAsync)} threw an exception: {ex}", nameof(RemoveModuleAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing module: device Id:{deviceId} moduleId: {moduleId}", nameof(RemoveModuleAsync)); + } + } /// /// Deletes a previously registered module from device in the system. /// /// The module being deleted. - public abstract Task RemoveModuleAsync(Module module); + public virtual Task RemoveModuleAsync(Module module) + { + return RemoveModuleAsync(module, CancellationToken.None); + } /// /// Deletes a previously registered module from device in the system. /// /// The module being deleted. /// The token which allows the operation to be canceled. - public abstract Task RemoveModuleAsync(Module module, CancellationToken cancellationToken); + public virtual Task RemoveModuleAsync(Module module, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing module: device Id:{module?.DeviceId} moduleId: {module?.Id}", nameof(RemoveModuleAsync)); + + try + { + EnsureInstanceNotClosed(); + + ValidateModuleId(module); + + return string.IsNullOrWhiteSpace(module.ETag) + ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice) + : RemoveDeviceModuleAsync(module.DeviceId, module.Id, module, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveModuleAsync)} threw an exception: {ex}", nameof(RemoveModuleAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing module: device Id:{module?.DeviceId} moduleId: {module?.Id}", nameof(RemoveModuleAsync)); + } + } /// /// Deletes a list of previously registered devices from the system. /// /// The devices being deleted. [Obsolete("Use RemoveDevices2Async")] - public abstract Task RemoveDevicesAsync(IEnumerable devices); + public virtual Task RemoveDevicesAsync(IEnumerable devices) + { + return RemoveDevicesAsync(devices, false, CancellationToken.None); + } /// /// Deletes a list of previously registered devices from the system. @@ -381,14 +916,36 @@ public void Dispose() /// Forces the device object to be removed without regard for an ETag match. /// The token which allows the operation to be canceled. [Obsolete("Use RemoveDevices2Async")] - public abstract Task RemoveDevicesAsync(IEnumerable devices, bool forceRemove, CancellationToken cancellationToken); + public virtual Task RemoveDevicesAsync(IEnumerable devices, bool forceRemove, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); + try + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForBulkOperations(devices, forceRemove ? ImportMode.Delete : ImportMode.DeleteIfMatchETag), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveDevicesAsync)} threw an exception: {ex}", nameof(RemoveDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); + } + } /// /// Deletes a list of previously registered devices from the system. /// /// The devices being deleted. /// Returns a BulkRegistryOperationResult object. - public abstract Task RemoveDevices2Async(IEnumerable devices); + public virtual Task RemoveDevices2Async(IEnumerable devices) + { + return RemoveDevices2Async(devices, false, CancellationToken.None); + } /// /// Deletes a list of previously registered devices from the system. @@ -397,25 +954,74 @@ public void Dispose() /// Forces the device object to be removed even if it was updated since it was retrieved last time. /// The token which allows the operation to be canceled. /// Returns a BulkRegistryOperationResult object. - public abstract Task RemoveDevices2Async(IEnumerable devices, bool forceRemove, CancellationToken cancellationToken); + public virtual Task RemoveDevices2Async(IEnumerable devices, bool forceRemove, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevices2Async)); + + try + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForBulkOperations(devices, forceRemove ? ImportMode.Delete : ImportMode.DeleteIfMatchETag), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveDevicesAsync)} threw an exception: {ex}", nameof(RemoveDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); + } + } /// /// Gets usage statistics for the IoT Hub. /// - public abstract Task GetRegistryStatisticsAsync(); + public virtual Task GetRegistryStatisticsAsync() + { + return GetRegistryStatisticsAsync(CancellationToken.None); + } /// /// Gets usage statistics for the IoT Hub. /// /// The token which allows the operation to be canceled. - public abstract Task GetRegistryStatisticsAsync(CancellationToken cancellationToken); + public virtual Task GetRegistryStatisticsAsync(CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting registry statistics", nameof(GetRegistryStatisticsAsync)); + + try + { + EnsureInstanceNotClosed(); + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } + }; + + return _httpClientHelper.GetAsync(GetStatisticsUri(), errorMappingOverrides, null, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetRegistryStatisticsAsync)} threw an exception: {ex}", nameof(GetRegistryStatisticsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting registry statistics", nameof(GetRegistryStatisticsAsync)); + } + } /// /// Retrieves the specified Device object. /// /// The id of the device being retrieved. /// The Device object. - public abstract Task GetDeviceAsync(string deviceId); + public virtual Task GetDeviceAsync(string deviceId) + { + return GetDeviceAsync(deviceId, CancellationToken.None); + } /// /// Retrieves the specified Device object. @@ -423,7 +1029,34 @@ public void Dispose() /// The id of the device being retrieved. /// The token which allows the operation to be canceled. /// The Device object. - public abstract Task GetDeviceAsync(string deviceId, CancellationToken cancellationToken); + public virtual Task GetDeviceAsync(string deviceId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting device: {deviceId}", nameof(GetDeviceAsync)); + try + { + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); + } + + EnsureInstanceNotClosed(); + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } + }; + + return _httpClientHelper.GetAsync(GetRequestUri(deviceId), errorMappingOverrides, null, false, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetDeviceAsync)} threw an exception: {ex}", nameof(GetDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting device: {deviceId}", nameof(GetDeviceAsync)); + } + } /// /// Retrieves the specified Module object. @@ -431,7 +1064,10 @@ public void Dispose() /// The id of the device being retrieved. /// The id of the module being retrieved. /// The Module object. - public abstract Task GetModuleAsync(string deviceId, string moduleId); + public virtual Task GetModuleAsync(string deviceId, string moduleId) + { + return GetModuleAsync(deviceId, moduleId, CancellationToken.None); + } /// /// Retrieves the specified Module object. @@ -440,14 +1076,53 @@ public void Dispose() /// The id of the module being retrieved. /// The token which allows the operation to be canceled. /// The Module object. - public abstract Task GetModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken); + public virtual Task GetModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting module: device Id: {deviceId} - module Id: {moduleId}", nameof(GetModuleAsync)); + + try + { + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); + } + + if (string.IsNullOrWhiteSpace(moduleId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "moduleId")); + } + + EnsureInstanceNotClosed(); + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.NotFound, + responseMessage => Task.FromResult(new ModuleNotFoundException(deviceId, moduleId)) + }, + }; + + return _httpClientHelper.GetAsync(GetModulesRequestUri(deviceId, moduleId), errorMappingOverrides, null, false, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetModuleAsync)} threw an exception: {ex}", nameof(GetModuleAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting module: device Id: {deviceId} - module Id: {moduleId}", nameof(GetModuleAsync)); + } + } /// /// Retrieves the module identities on device /// /// The device Id. /// List of modules on device. - public abstract Task> GetModulesOnDeviceAsync(string deviceId); + public virtual Task> GetModulesOnDeviceAsync(string deviceId) + { + return GetModulesOnDeviceAsync(deviceId, CancellationToken.None); + } /// /// Retrieves the module identities on device @@ -455,7 +1130,30 @@ public void Dispose() /// The device Id. /// The token which allows the operation to be canceled. /// List of modules on device. - public abstract Task> GetModulesOnDeviceAsync(string deviceId, CancellationToken cancellationToken); + public virtual Task> GetModulesOnDeviceAsync(string deviceId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting module on device: {deviceId}", nameof(GetModulesOnDeviceAsync)); + + try + { + EnsureInstanceNotClosed(); + + return _httpClientHelper.GetAsync>( + GetModulesOnDeviceRequestUri(deviceId), + null, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetModulesOnDeviceAsync)} threw an exception: {ex}", nameof(GetModulesOnDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting module on device: {deviceId}", nameof(GetModulesOnDeviceAsync)); + } + } /// /// Retrieves specified number of devices from every IoT Hub partition. @@ -463,7 +1161,10 @@ public void Dispose() /// /// The list of devices. [Obsolete("Use CreateQuery(\"select * from devices\", pageSize);")] - public abstract Task> GetDevicesAsync(int maxCount); + public virtual Task> GetDevicesAsync(int maxCount) + { + return GetDevicesAsync(maxCount, CancellationToken.None); + } /// /// Retrieves specified number of devices from every IoT hub partition. @@ -471,14 +1172,42 @@ public void Dispose() /// /// The list of devices. [Obsolete("Use CreateQuery(\"select * from devices\", pageSize);")] - public abstract Task> GetDevicesAsync(int maxCount, CancellationToken cancellationToken); + public virtual Task> GetDevicesAsync(int maxCount, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting devices - max count: {maxCount}", nameof(GetDevicesAsync)); + + try + { + EnsureInstanceNotClosed(); + + return _httpClientHelper.GetAsync>( + GetDevicesRequestUri(maxCount), + s_defaultGetDevicesOperationTimeout, + null, + null, + true, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetDevicesAsync)} threw an exception: {ex}", nameof(GetDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting devices - max count: {maxCount}", nameof(GetDevicesAsync)); + } + } /// /// Retrieves a handle through which a result for a given query can be fetched. /// /// The SQL query. /// A handle used to fetch results for a SQL query. - public abstract IQuery CreateQuery(string sqlQueryString); + public virtual IQuery CreateQuery(string sqlQueryString) + { + return CreateQuery(sqlQueryString, null); + } /// /// Retrieves a handle through which a result for a given query can be fetched. @@ -486,14 +1215,37 @@ public void Dispose() /// The SQL query. /// The maximum number of items per page. /// A handle used to fetch results for a SQL query. - public abstract IQuery CreateQuery(string sqlQueryString, int? pageSize); + public virtual IQuery CreateQuery(string sqlQueryString, int? pageSize) + { + Logging.Enter(this, $"Creating query", nameof(CreateQuery)); + try + { + return new Query((token) => ExecuteQueryAsync( + sqlQueryString, + pageSize, + token, + CancellationToken.None)); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(CreateQuery)} threw an exception: {ex}", nameof(CreateQuery)); + throw; + } + finally + { + Logging.Exit(this, $"Creating query", nameof(CreateQuery)); + } + } /// /// Copies registered device data to a set of blobs in a specific container in a storage account. /// /// ConnectionString to the destination StorageAccount. /// Destination blob container name. - public abstract Task ExportRegistryAsync(string storageAccountConnectionString, string containerName); + public virtual Task ExportRegistryAsync(string storageAccountConnectionString, string containerName) + { + return ExportRegistryAsync(storageAccountConnectionString, containerName, CancellationToken.None); + } /// /// Copies registered device data to a set of blobs in a specific container in a storage account. @@ -501,14 +1253,49 @@ public void Dispose() /// ConnectionString to the destination StorageAccount. /// Destination blob container name. /// Task cancellation token. - public abstract Task ExportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken); + public virtual Task ExportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Exporting registry", nameof(ExportRegistryAsync)); + try + { + EnsureInstanceNotClosed(); + + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } + }; + + return _httpClientHelper.PostAsync( + GetAdminUri("exportRegistry"), + new ExportImportRequest + { + ContainerName = containerName, + StorageConnectionString = storageAccountConnectionString, + }, + errorMappingOverrides, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(ExportRegistryAsync)} threw an exception: {ex}", nameof(ExportRegistryAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Exporting registry", nameof(ExportRegistryAsync)); + } + } /// /// Imports registered device data from a set of blobs in a specific container in a storage account. /// /// ConnectionString to the source StorageAccount. /// Source blob container name. - public abstract Task ImportRegistryAsync(string storageAccountConnectionString, string containerName); + public virtual Task ImportRegistryAsync(string storageAccountConnectionString, string containerName) + { + return ImportRegistryAsync(storageAccountConnectionString, containerName, CancellationToken.None); + } /// /// Imports registered device data from a set of blobs in a specific container in a storage account. @@ -516,7 +1303,40 @@ public void Dispose() /// ConnectionString to the source StorageAccount. /// Source blob container name. /// Task cancellation token. - public abstract Task ImportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken); + public virtual Task ImportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Importing registry", nameof(ImportRegistryAsync)); + + try + { + EnsureInstanceNotClosed(); + + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } + }; + + return _httpClientHelper.PostAsync( + GetAdminUri("importRegistry"), + new ExportImportRequest + { + ContainerName = containerName, + StorageConnectionString = storageAccountConnectionString, + }, + errorMappingOverrides, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(ImportRegistryAsync)} threw an exception: {ex}", nameof(ImportRegistryAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Importing registry", nameof(ImportRegistryAsync)); + } + } #pragma warning disable CA1054 // Uri parameters should not be strings @@ -527,7 +1347,13 @@ public void Dispose() /// Specifies whether to exclude the Device's Keys during the export. /// JobProperties of the newly created job. - public abstract Task ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys); + public virtual Task ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys) + { + return ExportDevicesAsync( + JobProperties.CreateForExportJob( + exportBlobContainerUri, + excludeKeys)); + } /// /// Creates a new bulk job to export device registrations to the container specified by the provided URI. @@ -536,7 +1362,14 @@ public void Dispose() /// Specifies whether to exclude the Device's Keys during the export. /// Task cancellation token. /// JobProperties of the newly created job. - public abstract Task ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys, CancellationToken cancellationToken); + public virtual Task ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys, CancellationToken cancellationToken) + { + return ExportDevicesAsync( + JobProperties.CreateForExportJob( + exportBlobContainerUri, + excludeKeys), + cancellationToken); + } /// /// Creates a new bulk job to export device registrations to the container specified by the provided URI. @@ -545,7 +1378,14 @@ public void Dispose() /// The name of the blob that will be created in the provided output blob container. /// Specifies whether to exclude the Device's Keys during the export. /// JobProperties of the newly created job. - public abstract Task ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys); + public virtual Task ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys) + { + return ExportDevicesAsync( + JobProperties.CreateForExportJob( + exportBlobContainerUri, + excludeKeys, + outputBlobName)); + } /// /// Creates a new bulk job to export device registrations to the container specified by the provided URI. @@ -555,7 +1395,15 @@ public void Dispose() /// Specifies whether to exclude the Device's Keys during the export. /// Task cancellation token. /// JobProperties of the newly created job. - public abstract Task ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys, CancellationToken cancellationToken); + public virtual Task ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys, CancellationToken cancellationToken) + { + return ExportDevicesAsync( + JobProperties.CreateForExportJob( + exportBlobContainerUri, + excludeKeys, + outputBlobName), + cancellationToken); + } /// /// Creates a new bulk job to export device registrations to the container specified by the provided URI. @@ -563,7 +1411,30 @@ public void Dispose() /// Parameters for the job. /// Task cancellation token. /// JobProperties of the newly created job. - public abstract Task ExportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default); + public virtual Task ExportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default) + { + if (jobParameters == null) + { + throw new ArgumentNullException(nameof(jobParameters)); + } + + Logging.Enter(this, $"Export Job running with {jobParameters}", nameof(ExportDevicesAsync)); + + try + { + jobParameters.Type = JobType.ExportDevices; + return CreateJobAsync(jobParameters, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(ExportDevicesAsync)} threw an exception: {ex}", nameof(ExportDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Export Job running with {jobParameters}", nameof(ExportDevicesAsync)); + } + } /// /// Creates a new bulk job to import device registrations into the IoT Hub. @@ -571,7 +1442,13 @@ public void Dispose() /// Source blob container URI. /// Destination blob container URI. /// JobProperties of the newly created job. - public abstract Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri); + public virtual Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri) + { + return ImportDevicesAsync( + JobProperties.CreateForImportJob( + importBlobContainerUri, + outputBlobContainerUri)); + } /// /// Creates a new bulk job to import device registrations into the IoT Hub. @@ -580,7 +1457,14 @@ public void Dispose() /// Destination blob container URI. /// Task cancellation token. /// JobProperties of the newly created job. - public abstract Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, CancellationToken cancellationToken); + public virtual Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, CancellationToken cancellationToken) + { + return ImportDevicesAsync( + JobProperties.CreateForImportJob( + importBlobContainerUri, + outputBlobContainerUri), + cancellationToken); + } /// /// Creates a new bulk job to import device registrations into the IoT Hub. @@ -589,7 +1473,14 @@ public void Dispose() /// Destination blob container URI. /// The blob name to be used when importing from the provided input blob container. /// JobProperties of the newly created job. - public abstract Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName); + public virtual Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName) + { + return ImportDevicesAsync( + JobProperties.CreateForImportJob( + importBlobContainerUri, + outputBlobContainerUri, + inputBlobName)); + } /// /// Creates a new bulk job to import device registrations into the IoT Hub. @@ -599,7 +1490,15 @@ public void Dispose() /// The blob name to be used when importing from the provided input blob container. /// Task cancellation token. /// JobProperties of the newly created job. - public abstract Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName, CancellationToken cancellationToken); + public virtual Task ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName, CancellationToken cancellationToken) + { + return ImportDevicesAsync( + JobProperties.CreateForImportJob( + importBlobContainerUri, + outputBlobContainerUri, + inputBlobName), + cancellationToken); + } #pragma warning restore CA1054 // Uri parameters should not be strings @@ -609,14 +1508,39 @@ public void Dispose() /// Parameters for the job. /// Task cancellation token. /// JobProperties of the newly created job. - public abstract Task ImportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default); + public virtual Task ImportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default) + { + if (jobParameters == null) + { + throw new ArgumentNullException(nameof(jobParameters)); + } + + Logging.Enter(this, $"Import Job running with {jobParameters}", nameof(ImportDevicesAsync)); + try + { + jobParameters.Type = JobType.ImportDevices; + return CreateJobAsync(jobParameters, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(ExportDevicesAsync)} threw an exception: {ex}", nameof(ImportDevicesAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Import Job running with {jobParameters}", nameof(ImportDevicesAsync)); + } + } /// /// Gets the job with the specified Id. /// /// Id of the Job object to retrieve. /// JobProperties of the job specified by the provided jobId. - public abstract Task GetJobAsync(string jobId); + public virtual Task GetJobAsync(string jobId) + { + return GetJobAsync(jobId, CancellationToken.None); + } /// /// Gets the job with the specified Id. @@ -624,40 +1548,131 @@ public void Dispose() /// Id of the Job object to retrieve. /// Task cancellation token. /// JobProperties of the job specified by the provided jobId. - public abstract Task GetJobAsync(string jobId, CancellationToken cancellationToken); + public virtual Task GetJobAsync(string jobId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting job {jobId}", nameof(GetJobsAsync)); + try + { + EnsureInstanceNotClosed(); + + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new JobNotFoundException(jobId)) } + }; + + return _httpClientHelper.GetAsync( + GetJobUri("/{0}".FormatInvariant(jobId)), + errorMappingOverrides, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting job {jobId}", nameof(GetJobsAsync)); + } + } /// /// List all jobs for the IoT Hub. /// /// IEnumerable of JobProperties of all jobs for this IoT Hub. - public abstract Task> GetJobsAsync(); + public virtual Task> GetJobsAsync() + { + return GetJobsAsync(CancellationToken.None); + } /// /// List all jobs for the IoT Hub. /// /// Task cancellation token. /// IEnumerable of JobProperties of all jobs for this IoT Hub. - public abstract Task> GetJobsAsync(CancellationToken cancellationToken); + public virtual Task> GetJobsAsync(CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting job", nameof(GetJobsAsync)); + try + { + EnsureInstanceNotClosed(); + + return _httpClientHelper.GetAsync>( + GetJobUri(string.Empty), + null, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting job", nameof(GetJobsAsync)); + } + } /// /// Cancels/Deletes the job with the specified Id. /// /// Id of the job to cancel. - public abstract Task CancelJobAsync(string jobId); + public virtual Task CancelJobAsync(string jobId) + { + return CancelJobAsync(jobId, CancellationToken.None); + } /// /// Cancels/Deletes the job with the specified Id. /// /// Id of the job to cancel. /// Task cancellation token. - public abstract Task CancelJobAsync(string jobId, CancellationToken cancellationToken); + public virtual Task CancelJobAsync(string jobId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Canceling job: {jobId}", nameof(CancelJobAsync)); + try + { + EnsureInstanceNotClosed(); + + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new JobNotFoundException(jobId)) } + }; + + IETagHolder jobETag = new ETagHolder + { + ETag = jobId, + }; + + return _httpClientHelper.DeleteAsync( + GetJobUri("/{0}".FormatInvariant(jobId)), + jobETag, + errorMappingOverrides, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting job {jobId}", nameof(GetJobsAsync)); + } + } /// /// Gets from IotHub /// /// The device Id. /// Twin instance. - public abstract Task GetTwinAsync(string deviceId); + public virtual Task GetTwinAsync(string deviceId) + { + return GetTwinAsync(deviceId, CancellationToken.None); + } /// /// Gets from IotHub @@ -665,7 +1680,34 @@ public void Dispose() /// The device Id. /// Task cancellation token. /// Twin instance. - public abstract Task GetTwinAsync(string deviceId, CancellationToken cancellationToken); + public virtual Task GetTwinAsync(string deviceId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting device twin on device: {deviceId}", nameof(GetTwinAsync)); + try + { + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); + } + + EnsureInstanceNotClosed(); + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } + }; + + return _httpClientHelper.GetAsync(GetTwinUri(deviceId), errorMappingOverrides, null, false, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetTwinAsync)} threw an exception: {ex}", nameof(GetTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting device twin on device: {deviceId}", nameof(GetTwinAsync)); + } + } /// /// Gets Module's from IotHub @@ -673,7 +1715,10 @@ public void Dispose() /// The device Id. /// The module Id. /// Twin instance. - public abstract Task GetTwinAsync(string deviceId, string moduleId); + public virtual Task GetTwinAsync(string deviceId, string moduleId) + { + return GetTwinAsync(deviceId, moduleId, CancellationToken.None); + } /// /// Gets Module's from IotHub @@ -682,7 +1727,40 @@ public void Dispose() /// The module Id. /// Task cancellation token. /// Twin instance. - public abstract Task GetTwinAsync(string deviceId, string moduleId, CancellationToken cancellationToken); + public virtual Task GetTwinAsync(string deviceId, string moduleId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting device twin on device: {deviceId} and module: {moduleId}", nameof(GetTwinAsync)); + + try + { + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); + } + + if (string.IsNullOrWhiteSpace(moduleId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "moduleId")); + } + + EnsureInstanceNotClosed(); + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.NotFound, async responseMessage => new ModuleNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) } + }; + + return _httpClientHelper.GetAsync(GetModuleTwinRequestUri(deviceId, moduleId), errorMappingOverrides, null, false, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetTwinAsync)} threw an exception: {ex}", nameof(GetTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting device twin on device: {deviceId} and module: {moduleId}", nameof(GetTwinAsync)); + } + } /// /// Updates the mutable fields of @@ -691,7 +1769,10 @@ public void Dispose() /// Twin with updated fields. /// Twin's ETag. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, Twin twinPatch, string etag); + public virtual Task UpdateTwinAsync(string deviceId, Twin twinPatch, string etag) + { + return UpdateTwinAsync(deviceId, twinPatch, etag, CancellationToken.None); + } /// /// Updates the mutable fields of @@ -701,7 +1782,10 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, Twin twinPatch, string etag, CancellationToken cancellationToken); + public virtual Task UpdateTwinAsync(string deviceId, Twin twinPatch, string etag, CancellationToken cancellationToken) + { + return UpdateTwinInternalAsync(deviceId, twinPatch, etag, false, cancellationToken); + } /// /// Updates the mutable fields of @@ -710,7 +1794,10 @@ public void Dispose() /// Twin json with updated fields. /// Twin's ETag. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag); + public virtual Task UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag) + { + return UpdateTwinAsync(deviceId, jsonTwinPatch, etag, CancellationToken.None); + } /// /// Updates the mutable fields of @@ -720,7 +1807,31 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag, CancellationToken cancellationToken); + public virtual Task UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating device twin on device: {deviceId}", nameof(UpdateTwinAsync)); + + try + { + if (string.IsNullOrWhiteSpace(jsonTwinPatch)) + { + throw new ArgumentNullException(nameof(jsonTwinPatch)); + } + + // TODO: Do we need to deserialize Twin, only to serialize it again? + Twin twin = JsonConvert.DeserializeObject(jsonTwinPatch); + return UpdateTwinAsync(deviceId, twin, etag, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Updating device twin on device: {deviceId}", nameof(UpdateTwinAsync)); + } + } /// /// Updates the mutable fields of Module's @@ -730,7 +1841,10 @@ public void Dispose() /// Twin with updated fields. /// Twin's ETag. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag); + public virtual Task UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag) + { + return UpdateTwinAsync(deviceId, moduleId, twinPatch, etag, CancellationToken.None); + } /// /// Updates the mutable fields of Module's @@ -741,7 +1855,10 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag, CancellationToken cancellationToken); + public virtual Task UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag, CancellationToken cancellationToken) + { + return UpdateTwinInternalAsync(deviceId, moduleId, twinPatch, etag, false, cancellationToken); + } /// /// Updates the mutable fields of Module's @@ -751,7 +1868,10 @@ public void Dispose() /// Twin json with updated fields. /// Twin's ETag. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag); + public virtual Task UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag) + { + return UpdateTwinAsync(deviceId, moduleId, jsonTwinPatch, etag, CancellationToken.None); + } /// /// Updates the mutable fields of Module's @@ -762,14 +1882,40 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag, CancellationToken cancellationToken); + public virtual Task UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating device twin on device: {deviceId} and module: {moduleId}", nameof(UpdateTwinAsync)); + try + { + if (string.IsNullOrWhiteSpace(jsonTwinPatch)) + { + throw new ArgumentNullException(nameof(jsonTwinPatch)); + } + + // TODO: Do we need to deserialize Twin, only to serialize it again? + Twin twin = JsonConvert.DeserializeObject(jsonTwinPatch); + return UpdateTwinAsync(deviceId, moduleId, twin, etag, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Updating device twin on device: {deviceId} and module: {moduleId}", nameof(UpdateTwinAsync)); + } + } /// /// Update the mutable fields for a list of s previously created within the system /// /// List of s with updated fields. /// Result of the bulk update operation. - public abstract Task UpdateTwins2Async(IEnumerable twins); + public virtual Task UpdateTwins2Async(IEnumerable twins) + { + return UpdateTwins2Async(twins, false, CancellationToken.None); + } /// /// Update the mutable fields for a list of s previously created within the system @@ -777,7 +1923,10 @@ public void Dispose() /// List of s with updated fields. /// Task cancellation token. /// Result of the bulk update operation. - public abstract Task UpdateTwins2Async(IEnumerable twins, CancellationToken cancellationToken); + public virtual Task UpdateTwins2Async(IEnumerable twins, CancellationToken cancellationToken) + { + return UpdateTwins2Async(twins, false, cancellationToken); + } /// /// Update the mutable fields for a list of s previously created within the system @@ -785,7 +1934,10 @@ public void Dispose() /// List of s with updated fields. /// Forces the object to be updated even if it has changed since it was retrieved last time. /// Result of the bulk update operation. - public abstract Task UpdateTwins2Async(IEnumerable twins, bool forceUpdate); + public virtual Task UpdateTwins2Async(IEnumerable twins, bool forceUpdate) + { + return UpdateTwins2Async(twins, forceUpdate, CancellationToken.None); + } /// /// Update the mutable fields for a list of s previously created within the system @@ -794,7 +1946,13 @@ public void Dispose() /// Forces the object to be updated even if it has changed since it was retrieved last time. /// Task cancellation token. /// Result of the bulk update operation. - public abstract Task UpdateTwins2Async(IEnumerable twins, bool forceUpdate, CancellationToken cancellationToken); + public virtual Task UpdateTwins2Async(IEnumerable twins, bool forceUpdate, CancellationToken cancellationToken) + { + return BulkDeviceOperationsAsync( + GenerateExportImportDeviceListForTwinBulkOperations(twins, forceUpdate ? ImportMode.UpdateTwin : ImportMode.UpdateTwinIfMatchETag), + ClientApiVersionHelper.ApiVersionQueryString, + cancellationToken); + } /// /// Updates the mutable fields of @@ -803,7 +1961,10 @@ public void Dispose() /// New Twin object to replace with. /// Twin's ETag. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, Twin newTwin, string etag); + public virtual Task ReplaceTwinAsync(string deviceId, Twin newTwin, string etag) + { + return ReplaceTwinAsync(deviceId, newTwin, etag, CancellationToken.None); + } /// /// Updates the mutable fields of @@ -813,7 +1974,10 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, Twin newTwin, string etag, CancellationToken cancellationToken); + public virtual Task ReplaceTwinAsync(string deviceId, Twin newTwin, string etag, CancellationToken cancellationToken) + { + return UpdateTwinInternalAsync(deviceId, newTwin, etag, true, cancellationToken); + } /// /// Updates the mutable fields of @@ -822,7 +1986,10 @@ public void Dispose() /// New Twin json to replace with. /// Twin's ETag. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, string newTwinJson, string etag); + public virtual Task ReplaceTwinAsync(string deviceId, string newTwinJson, string etag) + { + return ReplaceTwinAsync(deviceId, newTwinJson, etag, CancellationToken.None); + } /// /// Updates the mutable fields of @@ -832,7 +1999,30 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, string newTwinJson, string etag, CancellationToken cancellationToken); + public virtual Task ReplaceTwinAsync(string deviceId, string newTwinJson, string etag, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Replacing device twin on device: {deviceId}", nameof(ReplaceTwinAsync)); + try + { + if (string.IsNullOrWhiteSpace(newTwinJson)) + { + throw new ArgumentNullException(nameof(newTwinJson)); + } + + // TODO: Do we need to deserialize Twin, only to serialize it again? + Twin twin = JsonConvert.DeserializeObject(newTwinJson); + return ReplaceTwinAsync(deviceId, twin, etag, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(ReplaceTwinAsync)} threw an exception: {ex}", nameof(ReplaceTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Replacing device twin on device: {deviceId}", nameof(ReplaceTwinAsync)); + } + } /// /// Updates the mutable fields of Module's @@ -842,7 +2032,10 @@ public void Dispose() /// New Twin object to replace with. /// Twin's ETag. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag); + public virtual Task ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag) + { + return ReplaceTwinAsync(deviceId, moduleId, newTwin, etag, CancellationToken.None); + } /// /// Updates the mutable fields of Module's @@ -853,7 +2046,10 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag, CancellationToken cancellationToken); + public virtual Task ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag, CancellationToken cancellationToken) + { + return UpdateTwinInternalAsync(deviceId, moduleId, newTwin, etag, true, cancellationToken); + } /// /// Updates the mutable fields of Module's @@ -863,7 +2059,10 @@ public void Dispose() /// New Twin json to replace with. /// Twin's ETag. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag); + public virtual Task ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag) + { + return ReplaceTwinAsync(deviceId, moduleId, newTwinJson, etag, CancellationToken.None); + } /// /// Updates the mutable fields of Module's @@ -874,14 +2073,27 @@ public void Dispose() /// Twin's ETag. /// Task cancellation token. /// Updated Twin instance. - public abstract Task ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag, CancellationToken cancellationToken); + public virtual Task ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(newTwinJson)) + { + throw new ArgumentNullException(nameof(newTwinJson)); + } + + // TODO: Do we need to deserialize Twin, only to serialize it again? + Twin twin = JsonConvert.DeserializeObject(newTwinJson); + return ReplaceTwinAsync(deviceId, moduleId, twin, etag, cancellationToken); + } /// /// Register a new Configuration for Azure IOT Edge in IotHub /// /// The Configuration object being registered. /// The Configuration object. - public abstract Task AddConfigurationAsync(Configuration configuration); + public virtual Task AddConfigurationAsync(Configuration configuration) + { + return AddConfigurationAsync(configuration, CancellationToken.None); + } /// /// Register a new Configuration for Azure IOT Edge in IotHub @@ -889,14 +2101,50 @@ public void Dispose() /// The Configuration object being registered. /// The token which allows the operation to be canceled. /// The Configuration object. - public abstract Task AddConfigurationAsync(Configuration configuration, CancellationToken cancellationToken); + public virtual Task AddConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Adding configuration: {configuration?.Id}", nameof(AddConfigurationAsync)); + + try + { + EnsureInstanceNotClosed(); + + if (!string.IsNullOrEmpty(configuration.ETag)) + { + throw new ArgumentException(ApiResources.ETagSetWhileCreatingConfiguration); + } + + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + } + }; + + return _httpClientHelper.PutAsync(GetConfigurationRequestUri(configuration.Id), configuration, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(AddConfigurationAsync)} threw an exception: {ex}", nameof(AddConfigurationAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Adding configuration: {configuration?.Id}", nameof(AddConfigurationAsync)); + } + } /// /// Retrieves the specified Configuration object. /// /// The id of the Configuration being retrieved. /// The Configuration object. - public abstract Task GetConfigurationAsync(string configurationId); + public virtual Task GetConfigurationAsync(string configurationId) + { + return GetConfigurationAsync(configurationId, CancellationToken.None); + } /// /// Retrieves the specified Configuration object. @@ -904,28 +2152,84 @@ public void Dispose() /// The id of the Configuration being retrieved. /// The token which allows the operation to be canceled. /// The Configuration object. - public abstract Task GetConfigurationAsync(string configurationId, CancellationToken cancellationToken); + public virtual Task GetConfigurationAsync(string configurationId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting configuration: {configurationId}", nameof(GetConfigurationAsync)); + try + { + if (string.IsNullOrWhiteSpace(configurationId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "configurationId")); + } + + EnsureInstanceNotClosed(); + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.NotFound, async responseMessage => new ConfigurationNotFoundException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } + }; + + return _httpClientHelper.GetAsync(GetConfigurationRequestUri(configurationId), errorMappingOverrides, null, false, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetConfigurationAsync)} threw an exception: {ex}", nameof(GetConfigurationAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Get configuration: {configurationId}", nameof(GetConfigurationAsync)); + } + } /// /// Retrieves specified number of configurations from every IoT Hub partition. /// Results are not ordered. /// /// The list of configurations. - public abstract Task> GetConfigurationsAsync(int maxCount); + public virtual Task> GetConfigurationsAsync(int maxCount) + { + return GetConfigurationsAsync(maxCount, CancellationToken.None); + } /// /// Retrieves specified number of configurations from every IoT hub partition. /// Results are not ordered. /// /// The list of configurations. - public abstract Task> GetConfigurationsAsync(int maxCount, CancellationToken cancellationToken); + public virtual Task> GetConfigurationsAsync(int maxCount, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Getting configuration: max count: {maxCount}", nameof(GetConfigurationsAsync)); + try + { + EnsureInstanceNotClosed(); + + return _httpClientHelper.GetAsync>( + GetConfigurationsRequestUri(maxCount), + null, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetConfigurationsAsync)} threw an exception: {ex}", nameof(GetConfigurationsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting configuration: max count: {maxCount}", nameof(GetConfigurationsAsync)); + } + } /// /// Update the mutable fields of the Configuration registration /// /// The Configuration object with updated fields. /// The Configuration object with updated ETag. - public abstract Task UpdateConfigurationAsync(Configuration configuration); + public virtual Task UpdateConfigurationAsync(Configuration configuration) + { + return UpdateConfigurationAsync(configuration, CancellationToken.None); + } /// /// Update the mutable fields of the Configuration registration @@ -933,7 +2237,10 @@ public void Dispose() /// The Configuration object with updated fields. /// Forces the device object to be replaced without regard for an ETag match. /// The Configuration object with updated ETags. - public abstract Task UpdateConfigurationAsync(Configuration configuration, bool forceUpdate); + public virtual Task UpdateConfigurationAsync(Configuration configuration, bool forceUpdate) + { + return UpdateConfigurationAsync(configuration, forceUpdate, CancellationToken.None); + } /// /// Update the mutable fields of the Configuration registration @@ -941,7 +2248,10 @@ public void Dispose() /// The Configuration object with updated fields. /// The token which allows the operation to be canceled. /// The Configuration object with updated ETags. - public abstract Task UpdateConfigurationAsync(Configuration configuration, CancellationToken cancellationToken); + public virtual Task UpdateConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) + { + return UpdateConfigurationAsync(configuration, false, cancellationToken); + } /// /// Update the mutable fields of the Configuration registration @@ -950,40 +2260,136 @@ public void Dispose() /// Forces the Configuration object to be replaced even if it was updated since it was retrieved last time. /// The token which allows the operation to be canceled. /// The Configuration object with updated ETags. - public abstract Task UpdateConfigurationAsync(Configuration configuration, bool forceUpdate, CancellationToken cancellationToken); + public virtual Task UpdateConfigurationAsync(Configuration configuration, bool forceUpdate, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Updating configuration: {configuration?.Id} - Force update: {forceUpdate}", nameof(UpdateConfigurationAsync)); + + try + { + EnsureInstanceNotClosed(); + + if (string.IsNullOrWhiteSpace(configuration.ETag) && !forceUpdate) + { + throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingConfiguration); + } + + var errorMappingOverrides = new Dictionary>>() + { + { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, + { + HttpStatusCode.NotFound, async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return new ConfigurationNotFoundException(responseContent, (Exception)null); + } + } + }; + + PutOperationType operationType = forceUpdate + ? PutOperationType.ForceUpdateEntity + : PutOperationType.UpdateEntity; + + return _httpClientHelper.PutAsync(GetConfigurationRequestUri(configuration.Id), configuration, operationType, errorMappingOverrides, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateConfigurationAsync)} threw an exception: {ex}", nameof(UpdateConfigurationAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Updating configuration: {configuration?.Id} - Force update: {forceUpdate}", nameof(UpdateConfigurationAsync)); + } + } /// /// Deletes a previously registered device from the system. /// /// The id of the Configuration being deleted. - public abstract Task RemoveConfigurationAsync(string configurationId); + public virtual Task RemoveConfigurationAsync(string configurationId) + { + return RemoveConfigurationAsync(configurationId, CancellationToken.None); + } /// /// Deletes a previously registered device from the system. /// /// The id of the configurationId being deleted. /// The token which allows the operation to be canceled. - public abstract Task RemoveConfigurationAsync(string configurationId, CancellationToken cancellationToken); + public virtual Task RemoveConfigurationAsync(string configurationId, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing configuration: {configurationId}", nameof(RemoveConfigurationAsync)); + + try + { + EnsureInstanceNotClosed(); + + if (string.IsNullOrWhiteSpace(configurationId)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "configurationId")); + } + + // use wild-card ETag + var eTag = new ETagHolder { ETag = "*" }; + return RemoveConfigurationAsync(configurationId, eTag, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveConfigurationAsync)} threw an exception: {ex}", nameof(RemoveConfigurationAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing configuration: {configurationId}", nameof(RemoveConfigurationAsync)); + } + } /// /// Deletes a previously registered device from the system. /// /// The Configuration being deleted. - public abstract Task RemoveConfigurationAsync(Configuration configuration); + public virtual Task RemoveConfigurationAsync(Configuration configuration) + { + return RemoveConfigurationAsync(configuration, CancellationToken.None); + } /// /// Deletes a previously registered device from the system. /// /// The Configuration being deleted. /// The token which allows the operation to be canceled. - public abstract Task RemoveConfigurationAsync(Configuration configuration, CancellationToken cancellationToken); + public virtual Task RemoveConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Removing configuration: {configuration?.Id}", nameof(RemoveConfigurationAsync)); + try + { + EnsureInstanceNotClosed(); + + return string.IsNullOrWhiteSpace(configuration.ETag) + ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingConfiguration) + : RemoveConfigurationAsync(configuration.Id, configuration, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(RemoveConfigurationAsync)} threw an exception: {ex}", nameof(RemoveConfigurationAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Removing configuration: {configuration?.Id}", nameof(RemoveConfigurationAsync)); + } + } /// /// Applies configuration content to an IoTEdge device. /// /// The device Id. /// The configuration of an IoTEdge device. - public abstract Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content); + public virtual Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content) + { + return ApplyConfigurationContentOnDeviceAsync(deviceId, content, CancellationToken.None); + } /// /// Applies configuration content to an IoTEdge device. @@ -991,6 +2397,612 @@ public void Dispose() /// The device Id. /// The configuration of an IoTEdge device. /// The token which allows the operation to be canceled. - public abstract Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content, CancellationToken cancellationToken); + public virtual Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Applying configuration content on device: {deviceId}", nameof(ApplyConfigurationContentOnDeviceAsync)); + + try + { + return _httpClientHelper.PostAsync(GetApplyConfigurationOnDeviceRequestUri(deviceId), content, null, null, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(ApplyConfigurationContentOnDeviceAsync)} threw an exception: {ex}", nameof(ApplyConfigurationContentOnDeviceAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Applying configuration content on device: {deviceId}", nameof(ApplyConfigurationContentOnDeviceAsync)); + } + } + + private Task RemoveConfigurationAsync(string configurationId, IETagHolder eTagHolder, CancellationToken cancellationToken) + { + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.NotFound, + async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return new ConfigurationNotFoundException(responseContent, (Exception) null); + } + }, + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + } + }; + + return _httpClientHelper.DeleteAsync(GetConfigurationRequestUri(configurationId), eTagHolder, errorMappingOverrides, null, cancellationToken); + } + + private Task UpdateTwinInternalAsync(string deviceId, Twin twin, string etag, bool isReplace, CancellationToken cancellationToken) + { + EnsureInstanceNotClosed(); + + if (twin != null) + { + twin.DeviceId = deviceId; + } + + ValidateTwinId(twin); + + if (string.IsNullOrEmpty(etag)) + { + throw new ArgumentNullException(nameof(etag)); + } + + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + { + HttpStatusCode.NotFound, + async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) + } + }; + + return isReplace + ? _httpClientHelper.PutAsync( + GetTwinUri(deviceId), + twin, + etag, + etag == _wildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, + errorMappingOverrides, + cancellationToken) + : _httpClientHelper.PatchAsync( + GetTwinUri(deviceId), + twin, + etag, + etag == _wildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, + errorMappingOverrides, + cancellationToken); + } + + private Task UpdateTwinInternalAsync(string deviceId, string moduleId, Twin twin, string etag, bool isReplace, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Replacing device twin on device: {deviceId} - module: {moduleId} - is replace: {isReplace}", nameof(UpdateTwinAsync)); + try + { + EnsureInstanceNotClosed(); + + if (twin != null) + { + twin.DeviceId = deviceId; + twin.ModuleId = moduleId; + } + + ValidateTwinId(twin); + + if (string.IsNullOrEmpty(etag)) + { + throw new ArgumentNullException(nameof(etag)); + } + + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + { + HttpStatusCode.NotFound, + async responseMessage => new ModuleNotFoundException( + await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), + (Exception)null) + } + }; + + return isReplace + ? _httpClientHelper.PutAsync( + GetModuleTwinRequestUri(deviceId, moduleId), + twin, + etag, + etag == _wildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, + errorMappingOverrides, + cancellationToken) + : _httpClientHelper.PatchAsync( + GetModuleTwinRequestUri(deviceId, moduleId), + twin, + etag, + etag == _wildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, + errorMappingOverrides, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Replacing device twin on device: {deviceId} - module: {moduleId} - is replace: {isReplace}", nameof(UpdateTwinAsync)); + } + } + + private async Task ExecuteQueryAsync(string sqlQueryString, int? pageSize, string continuationToken, CancellationToken cancellationToken) + { + EnsureInstanceNotClosed(); + + if (string.IsNullOrWhiteSpace(sqlQueryString)) + { + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrEmpty, nameof(sqlQueryString))); + } + + var customHeaders = new Dictionary(); + if (!string.IsNullOrWhiteSpace(continuationToken)) + { + customHeaders.Add(_continuationTokenHeader, continuationToken); + } + + if (pageSize != null) + { + customHeaders.Add(_pageSizeHeader, pageSize.ToString()); + } + + HttpResponseMessage response = await _httpClientHelper + .PostAsync( + QueryDevicesRequestUri(), + new QuerySpecification { Sql = sqlQueryString }, + null, + customHeaders, + new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }, + null, + cancellationToken) + .ConfigureAwait(false); + + return await QueryResult.FromHttpResponseAsync(response).ConfigureAwait(false); + } + + private Task CreateJobAsync(JobProperties jobProperties, CancellationToken ct) + { + EnsureInstanceNotClosed(); + + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.Forbidden, async (responseMessage) => new JobQuotaExceededException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false))} + }; + + string clientApiVersion = ClientApiVersionHelper.ApiVersionQueryString; + + return _httpClientHelper.PostAsync( + GetJobUri("/create", clientApiVersion), + jobProperties, + errorMappingOverrides, + null, + ct); + } + + private static Uri GetRequestUri(string deviceId) + { + deviceId = WebUtility.UrlEncode(deviceId); + return new Uri(_requestUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetModulesRequestUri(string deviceId, string moduleId) + { + deviceId = WebUtility.UrlEncode(deviceId); + moduleId = WebUtility.UrlEncode(moduleId); + return new Uri(_modulesRequestUriFormat.FormatInvariant(deviceId, moduleId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetModulesOnDeviceRequestUri(string deviceId) + { + deviceId = WebUtility.UrlEncode(deviceId); + return new Uri(_modulesOnDeviceRequestUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetModuleTwinRequestUri(string deviceId, string moduleId) + { + deviceId = WebUtility.UrlEncode(deviceId); + moduleId = WebUtility.UrlEncode(moduleId); + return new Uri(_moduleTwinUriFormat.FormatInvariant(deviceId, moduleId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetConfigurationRequestUri(string configurationId) + { + configurationId = WebUtility.UrlEncode(configurationId); + return new Uri(_configurationRequestUriFormat.FormatInvariant(configurationId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetConfigurationsRequestUri(int maxCount) + { + return new Uri(_configurationsRequestUriFormat.FormatInvariant(maxCount, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetApplyConfigurationOnDeviceRequestUri(string deviceId) + { + return new Uri(_applyConfigurationOnDeviceUriFormat.FormatInvariant(deviceId), UriKind.Relative); + } + + private static Uri GetBulkRequestUri(string apiVersionQueryString) + { + return new Uri(_requestUriFormat.FormatInvariant(string.Empty, apiVersionQueryString), UriKind.Relative); + } + + private static Uri GetJobUri(string jobId, string apiVersion = ClientApiVersionHelper.ApiVersionQueryString) + { + return new Uri(_jobsUriFormat.FormatInvariant(jobId, apiVersion), UriKind.Relative); + } + + private static Uri GetDevicesRequestUri(int maxCount) + { + return new Uri(_devicesRequestUriFormat.FormatInvariant(maxCount, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri QueryDevicesRequestUri() + { + return new Uri(_devicesQueryUriFormat, UriKind.Relative); + } + + private static Uri GetAdminUri(string operation) + { + return new Uri(_adminUriFormat.FormatInvariant(operation, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static Uri GetStatisticsUri() + { + return new Uri(_statisticsUriFormat, UriKind.Relative); + } + + private static Uri GetTwinUri(string deviceId) + { + deviceId = WebUtility.UrlEncode(deviceId); + return new Uri(_twinUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); + } + + private static void ValidateDeviceId(Device device) + { + if (device == null) + { + throw new ArgumentNullException(nameof(device)); + } + + if (string.IsNullOrWhiteSpace(device.Id)) + { + throw new ArgumentException("device.Id"); + } + + if (!s_deviceIdRegex.IsMatch(device.Id)) + { + throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(device.Id)); + } + } + + private static void ValidateTwinId(Twin twin) + { + if (twin == null) + { + throw new ArgumentNullException(nameof(twin)); + } + + if (string.IsNullOrWhiteSpace(twin.DeviceId)) + { + throw new ArgumentException("twin.DeviceId"); + } + + if (!s_deviceIdRegex.IsMatch(twin.DeviceId)) + { + throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(twin.DeviceId)); + } + } + + private static void ValidateModuleId(Module module) + { + if (module == null) + { + throw new ArgumentNullException(nameof(module)); + } + + if (string.IsNullOrWhiteSpace(module.DeviceId)) + { + throw new ArgumentException("module.Id"); + } + + if (string.IsNullOrWhiteSpace(module.Id)) + { + throw new ArgumentException("module.ModuleId"); + } + + if (!s_deviceIdRegex.IsMatch(module.DeviceId)) + { + throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(module.DeviceId)); + } + + if (!s_deviceIdRegex.IsMatch(module.Id)) + { + throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(module.Id)); + } + } + + private static void ValidateDeviceAuthentication(AuthenticationMechanism authentication, string deviceId) + { + if (authentication != null) + { + // Both symmetric keys and X.509 cert thumbprints cannot be specified for the same device + bool symmetricKeyIsSet = !authentication.SymmetricKey?.IsEmpty() ?? false; + bool x509ThumbprintIsSet = !authentication.X509Thumbprint?.IsEmpty() ?? false; + + if (symmetricKeyIsSet && x509ThumbprintIsSet) + { + throw new ArgumentException(ApiResources.DeviceAuthenticationInvalid.FormatInvariant(deviceId ?? string.Empty)); + } + + // Validate X.509 thumbprints or SymmetricKeys since we should not have both at the same time + if (x509ThumbprintIsSet) + { + authentication.X509Thumbprint.IsValid(true); + } + else if (symmetricKeyIsSet) + { + authentication.SymmetricKey.IsValid(true); + } + } + } + + private Task RemoveDeviceModuleAsync(string deviceId, string moduleId, IETagHolder eTagHolder, CancellationToken cancellationToken) + { + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.NotFound, + async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return new DeviceNotFoundException(responseContent, (Exception) null); + } + }, + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + }; + + return _httpClientHelper.DeleteAsync(GetModulesRequestUri(deviceId, moduleId), eTagHolder, errorMappingOverrides, null, cancellationToken); + } + + private void EnsureInstanceNotClosed() + { + if (_httpClientHelper == null) + { + throw new ObjectDisposedException("RegistryManager", ApiResources.RegistryManagerInstanceAlreadyClosed); + } + } + + private static void NormalizeDevice(Device device) + { + // auto generate keys if not specified + if (device.Authentication == null) + { + device.Authentication = new AuthenticationMechanism(); + } + + NormalizeAuthenticationInfo(device.Authentication); + } + + private static void NormalizeAuthenticationInfo(AuthenticationMechanism authenticationInfo) + { + //to make it backward compatible we set the type according to the values + //we don't set CA type - that has to be explicit + if (authenticationInfo.SymmetricKey != null && !authenticationInfo.SymmetricKey.IsEmpty()) + { + authenticationInfo.Type = AuthenticationType.Sas; + } + + if (authenticationInfo.X509Thumbprint != null && !authenticationInfo.X509Thumbprint.IsEmpty()) + { + authenticationInfo.Type = AuthenticationType.SelfSigned; + } + } + + private static void NormalizeExportImportDevice(ExportImportDevice device) + { + // auto generate keys if not specified + if (device.Authentication == null) + { + device.Authentication = new AuthenticationMechanism(); + } + + NormalizeAuthenticationInfo(device.Authentication); + } + + private static IEnumerable GenerateExportImportDeviceListForBulkOperations(IEnumerable devices, ImportMode importMode) + { + if (devices == null) + { + throw new ArgumentNullException(nameof(devices)); + } + + if (!devices.Any()) + { + throw new ArgumentException($"Parameter {nameof(devices)} cannot be empty."); + } + + var exportImportDeviceList = new List(devices.Count()); + foreach (Device device in devices) + { + ValidateDeviceId(device); + + switch (importMode) + { + case ImportMode.Create: + if (!string.IsNullOrWhiteSpace(device.ETag)) + { + throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); + } + break; + + case ImportMode.Update: + // No preconditions + break; + + case ImportMode.UpdateIfMatchETag: + if (string.IsNullOrWhiteSpace(device.ETag)) + { + throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); + } + break; + + case ImportMode.Delete: + // No preconditions + break; + + case ImportMode.DeleteIfMatchETag: + if (string.IsNullOrWhiteSpace(device.ETag)) + { + throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice); + } + break; + + default: + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.InvalidImportMode, importMode)); + } + + var exportImportDevice = new ExportImportDevice(device, importMode); + exportImportDeviceList.Add(exportImportDevice); + } + + return exportImportDeviceList; + } + + private static IEnumerable GenerateExportImportDeviceListForTwinBulkOperations(IEnumerable twins, ImportMode importMode) + { + if (twins == null) + { + throw new ArgumentNullException(nameof(twins)); + } + + if (!twins.Any()) + { + throw new ArgumentException($"Parameter {nameof(twins)} cannot be empty"); + } + + var exportImportDeviceList = new List(twins.Count()); + foreach (Twin twin in twins) + { + ValidateTwinId(twin); + + switch (importMode) + { + case ImportMode.UpdateTwin: + // No preconditions + break; + + case ImportMode.UpdateTwinIfMatchETag: + if (string.IsNullOrWhiteSpace(twin.ETag)) + { + throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingTwin); + } + break; + + default: + throw new ArgumentException(IotHubApiResources.GetString(ApiResources.InvalidImportMode, importMode)); + } + + var exportImportDevice = new ExportImportDevice + { + Id = twin.DeviceId, + ModuleId = twin.ModuleId, + ImportMode = importMode, + TwinETag = importMode == ImportMode.UpdateTwinIfMatchETag ? twin.ETag : null, + Tags = twin.Tags, + Properties = new ExportImportDevice.PropertyContainer(), + }; + exportImportDevice.Properties.DesiredProperties = twin.Properties?.Desired; + + exportImportDeviceList.Add(exportImportDevice); + } + + return exportImportDeviceList; + } + + private Task BulkDeviceOperationsAsync(IEnumerable devices, string version, CancellationToken cancellationToken) + { + Logging.Enter(this, $"Performing bulk device operation on : {devices?.Count()} devices. version: {version}", nameof(BulkDeviceOperationsAsync)); + try + { + BulkDeviceOperationSetup(devices); + + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, + { HttpStatusCode.RequestEntityTooLarge, async responseMessage => new TooManyDevicesException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, + { HttpStatusCode.BadRequest, async responseMessage => new ArgumentException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } + }; + + return _httpClientHelper.PostAsync, T>(GetBulkRequestUri(version), devices, errorMappingOverrides, null, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(BulkDeviceOperationsAsync)} threw an exception: {ex}", nameof(BulkDeviceOperationsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Performing bulk device operation on : {devices?.Count()} devices. version: {version}", nameof(BulkDeviceOperationsAsync)); + } + } + + private void BulkDeviceOperationSetup(IEnumerable devices) + { + EnsureInstanceNotClosed(); + + if (devices == null) + { + throw new ArgumentNullException(nameof(devices)); + } + + foreach (ExportImportDevice device in devices) + { + ValidateDeviceAuthentication(device.Authentication, device.Id); + + NormalizeExportImportDevice(device); + } + } + + private Task RemoveDeviceAsync(string deviceId, IETagHolder eTagHolder, CancellationToken cancellationToken) + { + var errorMappingOverrides = new Dictionary>> + { + { + HttpStatusCode.NotFound, + async responseMessage => + { + string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); + return new DeviceNotFoundException(responseContent, (Exception) null); + } + }, + { + HttpStatusCode.PreconditionFailed, + async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) + }, + }; + + return _httpClientHelper.DeleteAsync(GetRequestUri(deviceId), eTagHolder, errorMappingOverrides, null, cancellationToken); + } } } diff --git a/iothub/service/src/ServiceClient.cs b/iothub/service/src/ServiceClient.cs index bd2649b1a4..5ee7c3856a 100644 --- a/iothub/service/src/ServiceClient.cs +++ b/iothub/service/src/ServiceClient.cs @@ -2,8 +2,16 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Amqp; +using Microsoft.Azure.Amqp.Framing; +using Microsoft.Azure.Devices.Common; +using Microsoft.Azure.Devices.Common.Data; +using Microsoft.Azure.Devices.Common.Exceptions; using Microsoft.Azure.Devices.Shared; #if !NET451 @@ -39,14 +47,72 @@ public enum TransportType /// Contains methods that services can use to send messages to devices /// For more information, see /// - public abstract class ServiceClient : IDisposable + public class ServiceClient : IDisposable { + private const string _statisticsUriFormat = "/statistics/service?" + ClientApiVersionHelper.ApiVersionQueryString; + private const string _purgeMessageQueueFormat = "/devices/{0}/commands?" + ClientApiVersionHelper.ApiVersionQueryString; + private const string _deviceMethodUriFormat = "/twins/{0}/methods?" + ClientApiVersionHelper.ApiVersionQueryString; + private const string _moduleMethodUriFormat = "/twins/{0}/modules/{1}/methods?" + ClientApiVersionHelper.ApiVersionQueryString; + + private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); + + private readonly FaultTolerantAmqpObject _faultTolerantSendingLink; + private readonly string _sendingPath; + private readonly AmqpFeedbackReceiver _feedbackReceiver; + private readonly AmqpFileNotificationReceiver _fileNotificationReceiver; + private readonly IHttpClientHelper _httpClientHelper; + private readonly string _iotHubName; + private readonly ServiceClientOptions _clientOptions; + private readonly TimeSpan _openTimeout; + private readonly TimeSpan _operationTimeout; + + private int _sendingDeliveryTag; + + internal readonly IotHubConnection Connection; + /// - /// Make this constructor internal so that only this library may implement this abstract class. + /// Creates an instance of , provided for unit testing purposes only. + /// Use the CreateFromConnectionString method to create an instance to use the client. /// - internal ServiceClient() + public ServiceClient() { - TlsVersions.Instance.SetLegacyAcceptableVersions(); + } + + internal ServiceClient( + IotHubConnectionProperties connectionProperties, + bool useWebSocketOnly, + ServiceClientTransportSettings transportSettings, + ServiceClientOptions options) + { + Connection = new IotHubConnection(connectionProperties, useWebSocketOnly, transportSettings); ; + _openTimeout = IotHubConnection.DefaultOpenTimeout; + _operationTimeout = IotHubConnection.DefaultOperationTimeout; + _faultTolerantSendingLink = new FaultTolerantAmqpObject(CreateSendingLinkAsync, Connection.CloseLink); + _feedbackReceiver = new AmqpFeedbackReceiver(Connection); + _fileNotificationReceiver = new AmqpFileNotificationReceiver(Connection); + _iotHubName = connectionProperties.IotHubName; + _clientOptions = options; + _sendingPath = "/messages/deviceBound"; + _httpClientHelper = new HttpClientHelper( + connectionProperties.HttpsEndpoint, + connectionProperties, + ExceptionHandlingHelper.GetDefaultErrorMapping(), + s_defaultOperationTimeout, + transportSettings.HttpProxy, + transportSettings.ConnectionLeaseTimeoutMilliseconds); + + // Set the trace provider for the AMQP library. + AmqpTrace.Provider = new AmqpTransportLog(); + } + + // internal test helper + internal ServiceClient(IotHubConnection connection, IHttpClientHelper httpClientHelper) + { + Connection = connection; + _httpClientHelper = httpClientHelper; + _feedbackReceiver = new AmqpFeedbackReceiver(Connection); + _fileNotificationReceiver = new AmqpFileNotificationReceiver(Connection); + _faultTolerantSendingLink = new FaultTolerantAmqpObject(CreateSendingLinkAsync, Connection.CloseLink); } /// @@ -94,7 +160,7 @@ public static ServiceClient CreateFromConnectionString(string connectionString, var tokenCredentialProperties = new IotHubTokenCrendentialProperties(hostName, credential); bool useWebSocketOnly = transportType == TransportType.Amqp_WebSocket_Only; - return new AmqpServiceClient( + return new ServiceClient( tokenCredentialProperties, useWebSocketOnly, transportSettings ?? new ServiceClientTransportSettings(), @@ -130,7 +196,7 @@ public static ServiceClient CreateFromConnectionString(string connectionString, var sasCredentialProperties = new IotHubSasCredentialProperties(hostName, credential); bool useWebSocketOnly = transportType == TransportType.Amqp_WebSocket_Only; - return new AmqpServiceClient( + return new ServiceClient( sasCredentialProperties, useWebSocketOnly, transportSettings ?? new ServiceClientTransportSettings(), @@ -150,7 +216,17 @@ public void Dispose() /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) { } + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _faultTolerantSendingLink.Dispose(); + _fileNotificationReceiver.Dispose(); + _feedbackReceiver.Dispose(); + Connection.Dispose(); + _httpClientHelper.Dispose(); + } + } /// /// Create an instance of ServiceClient from the specified IoT Hub connection string using specified Transport Type. @@ -181,55 +257,181 @@ public static ServiceClient CreateFromConnectionString(string connectionString, var iotHubConnectionString = IotHubConnectionString.Parse(connectionString); bool useWebSocketOnly = transportType == TransportType.Amqp_WebSocket_Only; - var serviceClient = new AmqpServiceClient(iotHubConnectionString, useWebSocketOnly, transportSettings, options); - return serviceClient; + + return new ServiceClient( + iotHubConnectionString, + useWebSocketOnly, + transportSettings, + options); } /// - /// Open the ServiceClient instance. + /// Open the ServiceClient instance. This call is made over AMQP. /// - public abstract Task OpenAsync(); + public virtual async Task OpenAsync() + { + Logging.Enter(this, $"Opening AmqpServiceClient", nameof(OpenAsync)); + + await _faultTolerantSendingLink.OpenAsync(_openTimeout).ConfigureAwait(false); + await _feedbackReceiver.OpenAsync().ConfigureAwait(false); + + Logging.Exit(this, $"Opening AmqpServiceClient", nameof(OpenAsync)); + } /// - /// Close the ServiceClient instance. + /// Close the ServiceClient instance. This call is made over AMQP. /// - public abstract Task CloseAsync(); + public virtual async Task CloseAsync() + { + Logging.Enter(this, $"Closing AmqpServiceClient", nameof(CloseAsync)); + + await _faultTolerantSendingLink.CloseAsync().ConfigureAwait(false); + await _feedbackReceiver.CloseAsync().ConfigureAwait(false); + await _fileNotificationReceiver.CloseAsync().ConfigureAwait(false); + await Connection.CloseAsync().ConfigureAwait(false); + + Logging.Exit(this, $"Closing AmqpServiceClient", nameof(CloseAsync)); + } /// - /// Send a cloud-to-device message to the specified device. + /// Send a cloud-to-device message to the specified device. This call is made over AMQP. /// /// The device identifier for the target device. /// The cloud-to-device message. /// The operation timeout, which defaults to 1 minute if unspecified. - public abstract Task SendAsync(string deviceId, Message message, TimeSpan? timeout = null); + public virtual async Task SendAsync(string deviceId, Message message, TimeSpan? timeout = null) + { + Logging.Enter(this, $"Sending message with Id [{message?.MessageId}] for device {deviceId}", nameof(SendAsync)); + + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentNullException(nameof(deviceId)); + } + + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + if (_clientOptions?.SdkAssignsMessageId == SdkAssignsMessageId.WhenUnset && message.MessageId == null) + { + message.MessageId = Guid.NewGuid().ToString(); + } + + if (message.IsBodyCalled) + { + message.ResetBody(); + } + + timeout ??= _operationTimeout; + + using AmqpMessage amqpMessage = MessageConverter.MessageToAmqpMessage(message); + amqpMessage.Properties.To = "/devices/" + WebUtility.UrlEncode(deviceId) + "/messages/deviceBound"; + + try + { + SendingAmqpLink sendingLink = await GetSendingLinkAsync().ConfigureAwait(false); + Outcome outcome = await sendingLink + .SendMessageAsync(amqpMessage, IotHubConnection.GetNextDeliveryTag(ref _sendingDeliveryTag), AmqpConstants.NullBinary, timeout.Value) + .ConfigureAwait(false); + + Logging.Info(this, $"Outcome was: {outcome?.DescriptorName}", nameof(SendAsync)); + + if (outcome.DescriptorCode != Accepted.Code) + { + throw AmqpErrorMapper.GetExceptionFromOutcome(outcome); + } + } + catch (Exception ex) when (!(ex is TimeoutException) && !ex.IsFatal()) + { + Logging.Error(this, $"{nameof(SendAsync)} threw an exception: {ex}", nameof(SendAsync)); + throw AmqpClientHelper.ToIotHubClientContract(ex); + } + finally + { + Logging.Exit(this, $"Sending message [{message?.MessageId}] for device {deviceId}", nameof(SendAsync)); + } + } /// - /// Removes all cloud-to-device messages from a device's queue. + /// Removes all cloud-to-device messages from a device's queue. This call is made over HTTP. /// /// The device identifier for the target device. /// A cancellation token to cancel the operation. - public abstract Task PurgeMessageQueueAsync(string deviceId, CancellationToken cancellationToken = default); + public virtual Task PurgeMessageQueueAsync(string deviceId, CancellationToken cancellationToken = default) + { + Logging.Enter(this, $"Purging message queue for device: {deviceId}", nameof(PurgeMessageQueueAsync)); + + try + { + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new DeviceNotFoundException(deviceId)) } + }; + + return _httpClientHelper.DeleteAsync(GetPurgeMessageQueueAsyncUri(deviceId), errorMappingOverrides, null, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(PurgeMessageQueueAsync)} threw an exception: {ex}", nameof(PurgeMessageQueueAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Purging message queue for device: {deviceId}", nameof(PurgeMessageQueueAsync)); + } + } /// /// Get the which can deliver acknowledgments for messages sent to a device/module from IoT Hub. + /// This call is made over AMQP. /// For more information see . /// /// An instance of . - public abstract FeedbackReceiver GetFeedbackReceiver(); + public virtual FeedbackReceiver GetFeedbackReceiver() + { + return _feedbackReceiver; + } /// /// Get the which can deliver notifications for file upload operations. + /// This call is made over AMQP. /// For more information see . /// /// An instance of . - public abstract FileNotificationReceiver GetFileNotificationReceiver(); + public virtual FileNotificationReceiver GetFileNotificationReceiver() + { + return _fileNotificationReceiver; + } /// - /// Gets service statistics for the IoT Hub. + /// Gets service statistics for the IoT Hub. This call is made over HTTP. /// /// A cancellation token to cancel the operation. /// The service statistics that can be retrieved from IoT Hub, eg. the number of devices connected to the hub. - public abstract Task GetServiceStatisticsAsync(CancellationToken cancellationToken = default); + public virtual Task GetServiceStatisticsAsync(CancellationToken cancellationToken = default) + { + Logging.Enter(this, $"Getting service statistics", nameof(GetServiceStatisticsAsync)); + + try + { + var errorMappingOverrides = new Dictionary>> + { + { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } + }; + + return _httpClientHelper.GetAsync(GetStatisticsUri(), errorMappingOverrides, null, cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(GetServiceStatisticsAsync)} threw an exception: {ex}", nameof(GetServiceStatisticsAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Getting service statistics", nameof(GetServiceStatisticsAsync)); + } + } /// /// Interactively invokes a method on a device. @@ -238,7 +440,10 @@ public static ServiceClient CreateFromConnectionString(string connectionString, /// Parameters to execute a direct method on the device. /// A cancellation token to cancel the operation. /// The . - public abstract Task InvokeDeviceMethodAsync(string deviceId, CloudToDeviceMethod cloudToDeviceMethod, CancellationToken cancellationToken = default); + public virtual Task InvokeDeviceMethodAsync(string deviceId, CloudToDeviceMethod cloudToDeviceMethod, CancellationToken cancellationToken = default) + { + return InvokeDeviceMethodAsync(GetDeviceMethodUri(deviceId), cloudToDeviceMethod, cancellationToken); + } /// /// Interactively invokes a method on a module. @@ -248,7 +453,20 @@ public static ServiceClient CreateFromConnectionString(string connectionString, /// Parameters to execute a direct method on the module. /// A cancellation token to cancel the operation. /// The . - public abstract Task InvokeDeviceMethodAsync(string deviceId, string moduleId, CloudToDeviceMethod cloudToDeviceMethod, CancellationToken cancellationToken = default); + public virtual Task InvokeDeviceMethodAsync(string deviceId, string moduleId, CloudToDeviceMethod cloudToDeviceMethod, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentNullException(nameof(deviceId)); + } + + if (string.IsNullOrWhiteSpace(moduleId)) + { + throw new ArgumentNullException(nameof(moduleId)); + } + + return InvokeDeviceMethodAsync(GetModuleMethodUri(deviceId, moduleId), cloudToDeviceMethod, cancellationToken); + } /// /// Send a cloud-to-device message to the specified module. @@ -257,6 +475,155 @@ public static ServiceClient CreateFromConnectionString(string connectionString, /// The module identifier for the target module. /// The cloud-to-module message. /// The operation timeout, which defaults to 1 minute if unspecified. - public abstract Task SendAsync(string deviceId, string moduleId, Message message, TimeSpan? timeout = null); + public virtual async Task SendAsync(string deviceId, string moduleId, Message message, TimeSpan? timeout = null) + { + Logging.Enter(this, $"Sending message with Id [{message?.MessageId}] for device {deviceId}, module {moduleId}", nameof(SendAsync)); + + if (string.IsNullOrWhiteSpace(deviceId)) + { + throw new ArgumentNullException(nameof(deviceId)); + } + + if (string.IsNullOrWhiteSpace(moduleId)) + { + throw new ArgumentNullException(nameof(moduleId)); + } + + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + if (_clientOptions?.SdkAssignsMessageId == SdkAssignsMessageId.WhenUnset && message.MessageId == null) + { + message.MessageId = Guid.NewGuid().ToString(); + } + + if (message.IsBodyCalled) + { + message.ResetBody(); + } + + timeout ??= _operationTimeout; + + using AmqpMessage amqpMessage = MessageConverter.MessageToAmqpMessage(message); + amqpMessage.Properties.To = "/devices/" + WebUtility.UrlEncode(deviceId) + "/modules/" + WebUtility.UrlEncode(moduleId) + "/messages/deviceBound"; + try + { + SendingAmqpLink sendingLink = await GetSendingLinkAsync().ConfigureAwait(false); + Outcome outcome = await sendingLink + .SendMessageAsync( + amqpMessage, + IotHubConnection.GetNextDeliveryTag(ref _sendingDeliveryTag), + AmqpConstants.NullBinary, + timeout.Value) + .ConfigureAwait(false); + + Logging.Info(this, $"Outcome was: {outcome?.DescriptorName}", nameof(SendAsync)); + + if (outcome.DescriptorCode != Accepted.Code) + { + throw AmqpErrorMapper.GetExceptionFromOutcome(outcome); + } + } + catch (Exception ex) when (!ex.IsFatal()) + { + Logging.Error(this, $"{nameof(SendAsync)} threw an exception: {ex}", nameof(SendAsync)); + throw AmqpClientHelper.ToIotHubClientContract(ex); + } + finally + { + Logging.Exit(this, $"Sending message with Id [{message?.MessageId}] for device {deviceId}, module {moduleId}", nameof(SendAsync)); + } + } + + private Task CreateSendingLinkAsync(TimeSpan timeout) + { + return Connection.CreateSendingLinkAsync(_sendingPath, timeout); + } + + private async Task GetSendingLinkAsync() + { + Logging.Enter(this, $"_faultTolerantSendingLink = {_faultTolerantSendingLink?.GetHashCode()}", nameof(GetSendingLinkAsync)); + + try + { + if (!_faultTolerantSendingLink.TryGetOpenedObject(out SendingAmqpLink sendingLink)) + { + sendingLink = await _faultTolerantSendingLink.GetOrCreateAsync(_openTimeout).ConfigureAwait(false); + } + + Logging.Info(this, $"Retrieved SendingAmqpLink [{sendingLink?.Name}]", nameof(GetSendingLinkAsync)); + + return sendingLink; + } + finally + { + Logging.Exit(this, $"_faultTolerantSendingLink = {_faultTolerantSendingLink?.GetHashCode()}", nameof(GetSendingLinkAsync)); + } + } + + private Task InvokeDeviceMethodAsync(Uri uri, + CloudToDeviceMethod cloudToDeviceMethod, + CancellationToken cancellationToken) + { + Logging.Enter(this, $"Invoking device method for: {uri}", nameof(InvokeDeviceMethodAsync)); + + try + { + TimeSpan timeout = GetInvokeDeviceMethodOperationTimeout(cloudToDeviceMethod); + + return _httpClientHelper.PostAsync( + uri, + cloudToDeviceMethod, + timeout, + null, + null, + cancellationToken); + } + catch (Exception ex) + { + Logging.Error(this, $"{nameof(InvokeDeviceMethodAsync)} threw an exception: {ex}", nameof(InvokeDeviceMethodAsync)); + throw; + } + finally + { + Logging.Exit(this, $"Invoking device method for: {uri}", nameof(InvokeDeviceMethodAsync)); + } + } + + private static TimeSpan GetInvokeDeviceMethodOperationTimeout(CloudToDeviceMethod cloudToDeviceMethod) + { + // For InvokeDeviceMethod, we need to take into account the timeouts specified + // for the Device to connect and send a response. We also need to take into account + // the transmission time for the request send/receive + var timeout = TimeSpan.FromSeconds(15); // For wire time + timeout += TimeSpan.FromSeconds(cloudToDeviceMethod.ConnectionTimeoutInSeconds ?? 0); + timeout += TimeSpan.FromSeconds(cloudToDeviceMethod.ResponseTimeoutInSeconds ?? 0); + return timeout <= s_defaultOperationTimeout ? s_defaultOperationTimeout : timeout; + } + + private static Uri GetStatisticsUri() + { + return new Uri(_statisticsUriFormat, UriKind.Relative); + } + + private static Uri GetPurgeMessageQueueAsyncUri(string deviceId) + { + return new Uri(_purgeMessageQueueFormat.FormatInvariant(deviceId), UriKind.Relative); + } + + private static Uri GetDeviceMethodUri(string deviceId) + { + deviceId = WebUtility.UrlEncode(deviceId); + return new Uri(_deviceMethodUriFormat.FormatInvariant(deviceId), UriKind.Relative); + } + + private static Uri GetModuleMethodUri(string deviceId, string moduleId) + { + deviceId = WebUtility.UrlEncode(deviceId); + moduleId = WebUtility.UrlEncode(moduleId); + return new Uri(_moduleMethodUriFormat.FormatInvariant(deviceId, moduleId), UriKind.Relative); + } } } diff --git a/iothub/service/tests/ConnectionString/ServiceClientConnectionStringTests.cs b/iothub/service/tests/ConnectionString/ServiceClientConnectionStringTests.cs index 0feae6597f..c46c6be1da 100644 --- a/iothub/service/tests/ConnectionString/ServiceClientConnectionStringTests.cs +++ b/iothub/service/tests/ConnectionString/ServiceClientConnectionStringTests.cs @@ -26,7 +26,7 @@ public virtual IotHubConnectionStringBuilder Populate(IotHubConnectionStringBuil public void ServiceClientConnectionStringDefaultScopeDefaultCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE="; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -36,7 +36,7 @@ public void ServiceClientConnectionStringDefaultScopeDefaultCredentialTypeTest() public void ServiceClientConnectionStringIotHubScopeImplicitSharedAccessSignatureCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;CredentialScope=IotHub;CredentialType=SharedAccessSignature;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE="; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -46,7 +46,7 @@ public void ServiceClientConnectionStringIotHubScopeImplicitSharedAccessSignatur public void ServiceClientConnectionStringIotHubScopeExplicitSharedAccessSignatureCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;CredentialScope=IotHub;CredentialType=SharedAccessSignature;SharedAccessKeyName=AllAccessKey;SharedAccessSignature=SharedAccessSignature sr=dh%3a%2f%2facme.azure-devices.net&sig=dGVzdFN0cmluZzU=&se=87824124985&skn=AllAccessKey"; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -56,7 +56,7 @@ public void ServiceClientConnectionStringIotHubScopeExplicitSharedAccessSignatur public void ServiceClientConnectionStringIotHubScopeSharedAccessKeyCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;CredentialScope=IotHub;CredentialType=SharedAccessKey;SharedAccessKeyName=AllAccessKey;SharedAccessKey=dGVzdFN0cmluZzE="; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -66,7 +66,7 @@ public void ServiceClientConnectionStringIotHubScopeSharedAccessKeyCredentialTyp public void ServiceClientConnectionStringDeviceScopeImplicitSharedAccessSignatureCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;CredentialScope=IotHub;CredentialType=SharedAccessSignature;SharedAccessKeyName=blah;SharedAccessKey=dGVzdFN0cmluZzE="; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -76,7 +76,7 @@ public void ServiceClientConnectionStringDeviceScopeImplicitSharedAccessSignatur public void ServiceClientConnectionStringDeviceScopeExplicitSharedAccessSignatureCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;CredentialScope=IotHub;CredentialType=SharedAccessSignature;SharedAccessKeyName=blah;SharedAccessSignature=SharedAccessSignature sr=dh%3a%2f%2facme.azure-devices.net&sig=dGVzdFN0cmluZzU=&se=87824124985&skn=AllAccessKey"; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -86,7 +86,7 @@ public void ServiceClientConnectionStringDeviceScopeExplicitSharedAccessSignatur public void ServiceClientConnectionStringDeviceScopeSharedAccessKeyCredentialTypeTest() { string connectionString = "HostName=acme.azure-devices.net;CredentialScope=IotHub;CredentialType=SharedAccessKey;SharedAccessKeyName=blah;SharedAccessKey=dGVzdFN0cmluZzE="; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); Assert.IsNotNull(serviceClient.Connection.Credential); @@ -179,7 +179,7 @@ public void ServiceClientIotHubConnectionStringBuilderTest() public void ServiceClient_ConnectionString_ModuleIdentity_SharedAccessKeyCredentialType_Test() { string connectionString = "HostName=testhub.azure-devices-int.net;DeviceId=edgecapabledevice1;ModuleId=testModule;SharedAccessKey=dGVzdFN0cmluZzE=;GatewayHostName=edgehub1.ms.com"; - var serviceClient = (AmqpServiceClient)ServiceClient.CreateFromConnectionString(connectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); Assert.IsNotNull(serviceClient.Connection); IotHubConnectionString iotHubConnectionString = (IotHubConnectionString)serviceClient.Connection.Credential; diff --git a/iothub/service/tests/DeviceAuthenticationTests.cs b/iothub/service/tests/DeviceAuthenticationTests.cs index 46db0a3d95..ac933dc3d7 100644 --- a/iothub/service/tests/DeviceAuthenticationTests.cs +++ b/iothub/service/tests/DeviceAuthenticationTests.cs @@ -42,7 +42,7 @@ public async Task DeviceAuthenticationGoodAuthConfigTest1() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceGoodAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceGoodAuthConfig).ConfigureAwait(false); } @@ -69,7 +69,7 @@ public async Task DeviceAuthenticationGoodAuthConfigTest2() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceGoodAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceGoodAuthConfig).ConfigureAwait(false); } @@ -96,7 +96,7 @@ public async Task DeviceAuthenticationGoodAuthConfigTest3() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceGoodAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceGoodAuthConfig).ConfigureAwait(false); } @@ -123,7 +123,7 @@ public async Task DeviceAuthenticationGoodAuthConfigTest4() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceGoodAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceGoodAuthConfig).ConfigureAwait(false); } @@ -150,7 +150,7 @@ public async Task DeviceAuthenticationGoodAuthConfigTest5() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceGoodAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceGoodAuthConfig).ConfigureAwait(false); } @@ -177,7 +177,7 @@ public async Task DeviceAuthenticationGoodAuthConfigTest6() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -204,7 +204,7 @@ public async Task DeviceAuthenticationGoodAuthSHA256() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -236,7 +236,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest1() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -268,7 +268,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest2() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -300,7 +300,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest3() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -332,7 +332,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest4() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -364,7 +364,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest5() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -392,7 +392,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest6() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -420,7 +420,7 @@ public async Task DeviceAuthenticationBadAuthConfigTest7() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -448,7 +448,7 @@ public async Task DeviceAuthenticationBadThumbprintTest1() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -476,7 +476,7 @@ public async Task DeviceAuthenticationBadThumbprintTest2() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -504,7 +504,7 @@ public async Task DeviceAuthenticationBadThumbprintTest3() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -532,7 +532,7 @@ public async Task DeviceAuthenticationBadThumbprintTest4() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -560,7 +560,7 @@ public async Task DeviceAuthenticationBadThumbprintTest5() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -588,7 +588,7 @@ public async Task DeviceAuthenticationBadThumbprintTest6() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -616,7 +616,7 @@ public async Task DeviceAuthenticationBadThumbprintTest7() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } @@ -644,7 +644,7 @@ public async Task DeviceAuthenticationBadThumbprintSHA256Test() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadAuthConfig); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadAuthConfig).ConfigureAwait(false); } @@ -668,7 +668,7 @@ public async Task DeviceAuthenticationIsCertificateAuthority() restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceBadThumbprint); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceBadThumbprint).ConfigureAwait(false); } } diff --git a/iothub/service/tests/JobClient/DeviceJobParametersTest.cs b/iothub/service/tests/JobClient/DeviceJobParametersTest.cs index fab4516111..4d3db405d1 100644 --- a/iothub/service/tests/JobClient/DeviceJobParametersTest.cs +++ b/iothub/service/tests/JobClient/DeviceJobParametersTest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace Microsoft.Azure.Devices.Api.Test.JobClient +namespace Microsoft.Azure.Devices.Api.Test { using System; using System.Collections.Generic; @@ -39,4 +39,4 @@ public void ConstructorWithSomeEmptyDeviceIdsTest() new DeviceJobParameters(JobType.ScheduleDeviceMethod, deviceList); } } -} \ No newline at end of file +} diff --git a/iothub/service/tests/JobClient/HttpJobClientTests.cs b/iothub/service/tests/JobClient/JobClientTests.cs similarity index 93% rename from iothub/service/tests/JobClient/HttpJobClientTests.cs rename to iothub/service/tests/JobClient/JobClientTests.cs index b804027803..ff88a02e57 100644 --- a/iothub/service/tests/JobClient/HttpJobClientTests.cs +++ b/iothub/service/tests/JobClient/JobClientTests.cs @@ -3,7 +3,7 @@ using System.Linq; -namespace Microsoft.Azure.Devices.Api.Test.JobClient +namespace Microsoft.Azure.Devices.Api.Test { using System; using System.Collections.Generic; @@ -16,20 +16,20 @@ namespace Microsoft.Azure.Devices.Api.Test.JobClient [TestClass] [TestCategory("Unit")] - public class HttpJobClientTests + public class JobClientTests { private readonly string jobId = "testJobId"; private readonly JobResponse expectedJobResponse = new JobResponse(); private readonly TimeSpan timeout = TimeSpan.FromMinutes(1); private Mock httpClientHelperMock; - private HttpJobClient jobClient; + private JobClient jobClient; [TestInitialize] public void Setup() { httpClientHelperMock = new Mock(); - jobClient = new HttpJobClient(httpClientHelperMock.Object); + jobClient = new JobClient(httpClientHelperMock.Object); } private void NoExtraJobParamTestSetup(JobType jobType, CancellationToken cancellationToken) diff --git a/iothub/service/tests/RegistryManagerTests.cs b/iothub/service/tests/RegistryManagerTests.cs index 12c26329f2..48e374c003 100644 --- a/iothub/service/tests/RegistryManagerTests.cs +++ b/iothub/service/tests/RegistryManagerTests.cs @@ -46,7 +46,7 @@ public async Task GetDeviceAsyncTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.GetAsync(It.IsAny(), It.IsAny>>>(), null, false, It.IsAny())).ReturnsAsync(deviceToReturn); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); var device = await registryManager.GetDeviceAsync(DeviceId).ConfigureAwait(false); Assert.AreSame(deviceToReturn, device); restOpMock.VerifyAll(); @@ -57,7 +57,7 @@ public async Task GetDeviceAsyncTest() public async Task GetDeviceAsyncWithNullDeviceIdTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.GetDeviceAsync(null).ConfigureAwait(false); Assert.Fail("Calling GetDeviceAsync with null device id did not throw an exception."); } @@ -76,7 +76,7 @@ public async Task GetDevicesAsyncTest() true, It.IsAny())).ReturnsAsync(devicesToReturn); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete var returnedDevices = await registryManager.GetDevicesAsync(1).ConfigureAwait(false); @@ -94,7 +94,7 @@ public async Task RegisterDeviceAsyncTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceToReturn); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); var returnedDevice = await registryManager.AddDeviceAsync(deviceToReturn).ConfigureAwait(false); Assert.AreSame(deviceToReturn, returnedDevice); restOpMock.VerifyAll(); @@ -106,7 +106,7 @@ public async Task RegisterDeviceAsyncWithInvalidDeviceIdTest() { var deviceToReturn = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected, ETag = "123" }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceToReturn).ConfigureAwait(false); Assert.Fail("RegisterDevice API did not throw exception when bad deviceid was used."); } @@ -117,7 +117,7 @@ public async Task RegisterDeviceAsyncWithETagSetTest() { var deviceToReturn = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "123" }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(deviceToReturn).ConfigureAwait(false); Assert.Fail("RegisterDevice API did not throw exception when ETag was set."); } @@ -127,7 +127,7 @@ public async Task RegisterDeviceAsyncWithETagSetTest() public async Task RegisterDeviceAsyncWithNullDeviceTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(null).ConfigureAwait(false); Assert.Fail("RegisterDevice API did not throw exception when the device parameter was null."); } @@ -137,7 +137,7 @@ public async Task RegisterDeviceAsyncWithNullDeviceTest() public async Task RegisterDeviceAsyncWithDeviceIdNullTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDeviceAsync(new Device()).ConfigureAwait(false); Assert.Fail("RegisterDevice API did not throw exception when the device's id was not set."); } @@ -150,7 +150,7 @@ public async Task RegisterDevicesAsyncWithInvalidDeviceIdTest() // '/' is not a valid character in DeviceId var badDevice = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.AddDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -165,7 +165,7 @@ public async Task RegisterDevices2AsyncWithInvalidDeviceIdTest() // '/' is not a valid character in DeviceId var badDevice = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("RegisterDevices API did not throw exception when bad deviceid was used."); } @@ -177,7 +177,7 @@ public async Task RegisterDevicesAsyncWithETagsSetTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device("234") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.AddDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -191,7 +191,7 @@ public async Task RegisterDevices2AsyncWithETagsSetTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device("234") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("RegisterDevices API did not throw exception when ETag was used."); } @@ -203,7 +203,7 @@ public async Task RegisterDevicesAsyncWithNullDeviceTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; Device badDevice = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.AddDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -217,7 +217,7 @@ public async Task RegisterDevices2AsyncWithNullDeviceTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; Device badDevice = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("RegisterDevices API did not throw exception when Null device was used."); } @@ -227,7 +227,7 @@ public async Task RegisterDevices2AsyncWithNullDeviceTest() public async Task RegisterDevicesAsyncWithNullDeviceListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.AddDevicesAsync(new List()).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -239,7 +239,7 @@ public async Task RegisterDevicesAsyncWithNullDeviceListTest() public async Task RegisterDevices2AsyncWithNullDeviceListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDevices2Async(new List()).ConfigureAwait(false); Assert.Fail("RegisterDevices API did not throw exception when Null device list was used."); } @@ -251,7 +251,7 @@ public async Task RegisterDevicesAsyncWithDeviceIdNullTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.AddDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -265,7 +265,7 @@ public async Task RegisterDevices2AsyncWithDeviceIdNullTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.AddDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("RegisterDevices API did not throw exception when deviceId was null."); } @@ -277,7 +277,7 @@ public async Task UpdateDeviceAsyncTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PutAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>>>(), It.IsAny())).ReturnsAsync(deviceToReturn); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); var returnedDevice = await registryManager.UpdateDeviceAsync(deviceToReturn).ConfigureAwait(false); Assert.AreSame(deviceToReturn, returnedDevice); restOpMock.VerifyAll(); @@ -294,7 +294,7 @@ private Device PrepareTestDevice(int batteryLevel, string firmwareVersion) public async Task UpdateDeviceWithNullDeviceTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDeviceAsync(null).ConfigureAwait(false); Assert.Fail("UpdateDevice api did not throw exception when the device parameter was null."); } @@ -304,7 +304,7 @@ public async Task UpdateDeviceWithNullDeviceTest() public async Task UpdateDeviceWithDeviceIdNullTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDeviceAsync(new Device() { ETag = "*" }).ConfigureAwait(false); Assert.Fail("UpdateDevice api did not throw exception when the device's id was null."); } @@ -314,7 +314,7 @@ public async Task UpdateDeviceWithDeviceIdNullTest() public async Task UpdateDeviceWithInvalidDeviceIdTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); // '/' is not a valid char in DeviceId await registryManager.UpdateDeviceAsync(new Device("/baddevice") { ETag = "*" }).ConfigureAwait(false); Assert.Fail("UpdateDevice api did not throw exception when the deviceid was invalid."); @@ -327,7 +327,7 @@ public async Task UpdateDevicesAsyncWithInvalidDeviceIdTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -341,7 +341,7 @@ public async Task UpdateDevices2AsyncWithInvalidDeviceIdTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("UpdateDevices API did not throw exception when bad deviceid was used."); } @@ -353,7 +353,7 @@ public async Task UpdateDevicesAsyncWithETagMissingTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device("234") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -367,7 +367,7 @@ public async Task UpdateDevices2AsyncWithETagMissingTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device("234") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("UpdateDevices API did not throw exception when ETag was null."); } @@ -379,7 +379,7 @@ public async Task UpdateDevicesAsyncWithNullDeviceTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; Device badDevice = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -393,7 +393,7 @@ public async Task UpdateDevices2AsyncWithNullDeviceTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; Device badDevice = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("UpdateDevices API did not throw exception when Null device was used."); } @@ -403,7 +403,7 @@ public async Task UpdateDevices2AsyncWithNullDeviceTest() public async Task UpdateDevicesAsyncWithNullDeviceListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List()).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -415,7 +415,7 @@ public async Task UpdateDevicesAsyncWithNullDeviceListTest() public async Task UpdateDevices2AsyncWithNullDeviceListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List()).ConfigureAwait(false); Assert.Fail("UpdateDevices API did not throw exception when Null device list was used."); } @@ -427,7 +427,7 @@ public async Task UpdateDevicesAsyncWithDeviceIdNullTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -441,7 +441,7 @@ public async Task UpdateDevices2AsyncWithDeviceIdNullTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("UpdateDevices API did not throw exception when deviceId was null."); } @@ -453,7 +453,7 @@ public async Task UpdateDevicesAsyncForceUpdateTest() var goodDevice2 = new Device("234") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { goodDevice1, goodDevice2 }, true, CancellationToken.None).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -467,7 +467,7 @@ public async Task UpdateDevices2AsyncForceUpdateTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { goodDevice1, goodDevice2 }, true, CancellationToken.None).ConfigureAwait(false); } @@ -480,7 +480,7 @@ public async Task UpdateDevicesAsyncForceUpdateMissingETagTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { badDevice1, badDevice2 }, false, CancellationToken.None).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -495,7 +495,7 @@ public async Task UpdateDevices2AsyncForceUpdateMissingETagTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { badDevice1, badDevice2 }, false, CancellationToken.None).ConfigureAwait(false); } @@ -507,7 +507,7 @@ public async Task UpdateDevicesAsyncForceUpdateFalseTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.UpdateDevicesAsync(new List() { goodDevice1, goodDevice2 }, false, CancellationToken.None).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -521,7 +521,7 @@ public async Task UpdateDevices2AsyncForceUpdateFalseTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateDevices2Async(new List() { goodDevice1, goodDevice2 }, false, CancellationToken.None).ConfigureAwait(false); } @@ -532,7 +532,7 @@ public async Task DeleteDeviceAsyncTest() var restOpMock = new Mock(); var mockETag = new ETagHolder() { ETag = "*" }; restOpMock.Setup(restOp => restOp.DeleteAsync(It.IsAny(), mockETag, It.IsAny>>>(), null, It.IsAny())).Returns(Task.FromResult(0)); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDeviceAsync(new Device()).ConfigureAwait(false); restOpMock.VerifyAll(); } @@ -542,7 +542,7 @@ public async Task DeleteDeviceAsyncTest() public async Task DeleteDeviceAsyncWithNullIdTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDeviceAsync(string.Empty).ConfigureAwait(false); Assert.Fail("Delete API did not throw exception when the device id was null."); } @@ -554,7 +554,7 @@ public async Task DeleteDevicesAsyncWithInvalidDeviceIdTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -568,7 +568,7 @@ public async Task DeleteDevices2AsyncWithInvalidDeviceIdTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected }; var badDevice = new Device("/baddevice") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("DeleteDevices API did not throw exception when bad deviceid was used."); } @@ -580,7 +580,7 @@ public async Task DeleteDevicesAsyncWithETagMissingTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device("234") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -594,7 +594,7 @@ public async Task DeleteDevices2AsyncWithETagMissingTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device("234") { ConnectionState = DeviceConnectionState.Connected }; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("DeleteDevices API did not throw exception when ETag was null."); } @@ -606,7 +606,7 @@ public async Task DeleteDevicesAsyncWithNullDeviceTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; Device badDevice = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -620,7 +620,7 @@ public async Task DeleteDevices2AsyncWithNullDeviceTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; Device badDevice = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("DeleteDevices API did not throw exception when Null device was used."); } @@ -630,7 +630,7 @@ public async Task DeleteDevices2AsyncWithNullDeviceTest() public async Task DeleteDevicesAsyncWithNullDeviceListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List()).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -642,7 +642,7 @@ public async Task DeleteDevicesAsyncWithNullDeviceListTest() public async Task DeleteDevices2AsyncWithNullDeviceListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List()).ConfigureAwait(false); Assert.Fail("DeleteDevices API did not throw exception when Null device list was used."); } @@ -654,7 +654,7 @@ public async Task DeleteDevicesAsyncWithDeviceIdNullTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { goodDevice, badDevice }).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -668,7 +668,7 @@ public async Task DeleteDevices2AsyncWithDeviceIdNullTest() var goodDevice = new Device("123") { ConnectionState = DeviceConnectionState.Connected, ETag = "234" }; var badDevice = new Device(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { goodDevice, badDevice }).ConfigureAwait(false); Assert.Fail("DeleteDevices API did not throw exception when deviceId was null."); } @@ -681,7 +681,7 @@ public async Task DeleteDevicesAsyncForceDeleteTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { goodDevice1, goodDevice2 }, true, CancellationToken.None).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -695,7 +695,7 @@ public async Task DeleteDevices2AsyncForceDeleteTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { goodDevice1, goodDevice2 }, true, CancellationToken.None).ConfigureAwait(false); } @@ -708,7 +708,7 @@ public async Task DeleteDevicesAsyncForceDeleteFalseMissingETagTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { badDevice1, badDevice2 }, false, CancellationToken.None).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -723,7 +723,7 @@ public async Task DeleteDevices2AsyncForceDeleteFalseMissingETagTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { badDevice1, badDevice2 }, false, CancellationToken.None).ConfigureAwait(false); } @@ -735,7 +735,7 @@ public async Task DeleteDevicesAsyncForceDeleteFalseTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); #pragma warning disable CS0618 // Type or member is obsolete await registryManager.RemoveDevicesAsync(new List() { goodDevice1, goodDevice2 }, false, CancellationToken.None).ConfigureAwait(false); #pragma warning restore CS0618 // Type or member is obsolete @@ -749,7 +749,7 @@ public async Task DeleteDevices2AsyncForceDeleteFalseTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.PostAsync, Task>(It.IsAny(), It.IsAny>(), It.IsAny>>>(), It.IsAny>(), It.IsAny())).ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.RemoveDevices2Async(new List() { goodDevice1, goodDevice2 }, false, CancellationToken.None).ConfigureAwait(false); } @@ -760,7 +760,7 @@ public async Task UpdateTwins2AsyncWithInvalidDeviceIdTest() var goodTwin = new Twin("123"); var badTwin = new Twin("/badTwin"); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { goodTwin, badTwin }).ConfigureAwait(false); Assert.Fail("UpdateTwins API did not throw exception when bad deviceid was used."); } @@ -772,7 +772,7 @@ public async Task UpdateTwins2AsyncWithETagMissingTest() var goodTwin = new Twin("123") { ETag = "234" }; var badTwin = new Twin("234"); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { goodTwin, badTwin }).ConfigureAwait(false); Assert.Fail("UpdateTwins API did not throw exception when ETag was null."); } @@ -784,7 +784,7 @@ public async Task UpdateTwins2AsyncWithNullTwinTest() var goodTwin = new Twin("123") { ETag = "234" }; Twin badTwin = null; var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { goodTwin, badTwin }).ConfigureAwait(false); Assert.Fail("UpdateTwins API did not throw exception when Null twin was used."); } @@ -794,7 +794,7 @@ public async Task UpdateTwins2AsyncWithNullTwinTest() public async Task UpdateTwins2AsyncWithNullTwinListTest() { var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List()).ConfigureAwait(false); Assert.Fail("UpdateTwins API did not throw exception when Null twin list was used."); } @@ -806,7 +806,7 @@ public async Task UpdateTwins2AsyncWithDeviceIdNullTest() var goodTwin = new Twin("123") { ETag = "234" }; var badTwin = new Twin(); var restOpMock = new Mock(); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { goodTwin, badTwin }).ConfigureAwait(false); Assert.Fail("UpdateTwins API did not throw exception when deviceId was null."); } @@ -826,7 +826,7 @@ public async Task UpdateTwins2AsyncForceUpdateTest() It.IsAny())) .ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { goodTwin1, goodTwin2 }, true, CancellationToken.None).ConfigureAwait(false); } @@ -846,7 +846,7 @@ public async Task UpdateTwins2AsyncForceUpdateMissingETagTest() It.IsAny())) .ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { badTwin1, badTwin2 }, false, CancellationToken.None).ConfigureAwait(false); } @@ -865,7 +865,7 @@ public async Task UpdateTwins2AsyncForceUpdateFalseTest() It.IsAny())) .ReturnsAsync((Task)null); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.UpdateTwins2Async(new List() { goodTwin1, goodTwin2 }, false, CancellationToken.None).ConfigureAwait(false); } @@ -875,7 +875,7 @@ public void DisposeTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.Dispose()); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); registryManager.Dispose(); restOpMock.Verify(restOp => restOp.Dispose(), Times.Once()); } @@ -886,7 +886,7 @@ public async Task CloseAsyncTest() var restOpMock = new Mock(); restOpMock.Setup(restOp => restOp.Dispose()); - var registryManager = new HttpRegistryManager(restOpMock.Object, IotHubName); + var registryManager = new RegistryManager(IotHubName, restOpMock.Object); await registryManager.CloseAsync().ConfigureAwait(false); restOpMock.Verify(restOp => restOp.Dispose(), Times.Never()); } diff --git a/iothub/service/tests/ServiceClientTests.cs b/iothub/service/tests/ServiceClientTests.cs index 4ba3b86fd1..bb94fc251a 100644 --- a/iothub/service/tests/ServiceClientTests.cs +++ b/iothub/service/tests/ServiceClientTests.cs @@ -23,9 +23,9 @@ public class ServiceClientTests public async Task PurgeMessageQueueWithCancellationTokenTest() { // Arrange Moq - Tuple, AmqpServiceClient, PurgeMessageQueueResult> setupParameters = this.SetupPurgeMessageQueueTests(); + Tuple, ServiceClient, PurgeMessageQueueResult> setupParameters = this.SetupPurgeMessageQueueTests(); Mock restOpMock = setupParameters.Item1; - AmqpServiceClient serviceClient = setupParameters.Item2; + ServiceClient serviceClient = setupParameters.Item2; PurgeMessageQueueResult expectedResult = setupParameters.Item3; // Execute method under test @@ -50,13 +50,17 @@ public async Task PurgeMessageQueueDeviceNotFoundTest() // Instantiate AmqpServiceClient with Mock IHttpClientHelper var authMethod = new ServiceAuthenticationWithSharedAccessPolicyKey("test", "dGVzdFN0cmluZzE="); var builder = IotHubConnectionStringBuilder.Create("acme.azure-devices.net", authMethod); - var serviceClient = new AmqpServiceClient(restOpMock.Object); + Func> onCreate = _ => Task.FromResult(new AmqpSession(null, new AmqpSessionSettings(), null)); + Action onClose = _ => { }; + // Instantiate AmqpServiceClient with Mock IHttpClientHelper and IotHubConnection + var connection = new IotHubConnection(onCreate, onClose); + var serviceClient = new ServiceClient(connection, restOpMock.Object); // Execute method under test PurgeMessageQueueResult result = await serviceClient.PurgeMessageQueueAsync("TestDevice", CancellationToken.None).ConfigureAwait(false); } - Tuple, AmqpServiceClient, PurgeMessageQueueResult> SetupPurgeMessageQueueTests() + private Tuple, ServiceClient, PurgeMessageQueueResult> SetupPurgeMessageQueueTests() { // Create expected return object var deviceId = "TestDevice"; @@ -76,7 +80,11 @@ public async Task PurgeMessageQueueDeviceNotFoundTest() // Instantiate AmqpServiceClient with Mock IHttpClientHelper var authMethod = new ServiceAuthenticationWithSharedAccessPolicyKey("test", "dGVzdFN0cmluZzE="); var builder = IotHubConnectionStringBuilder.Create("acme.azure-devices.net", authMethod); - var serviceClient = new AmqpServiceClient(restOpMock.Object); + Func> onCreate = _ => Task.FromResult(new AmqpSession(null, new AmqpSessionSettings(), null)); + Action onClose = _ => { }; + // Instantiate AmqpServiceClient with Mock IHttpClientHelper and IotHubConnection + var connection = new IotHubConnection(onCreate, onClose); + var serviceClient = new ServiceClient(connection, restOpMock.Object); return Tuple.Create(restOpMock, serviceClient, expectedResult); } @@ -91,7 +99,7 @@ public async Task DisposeTest() Action onClose = _ => { connectionClosed = true; }; // Instantiate AmqpServiceClient with Mock IHttpClientHelper and IotHubConnection var connection = new IotHubConnection(onCreate, onClose); - var serviceClient = new AmqpServiceClient(connection, restOpMock.Object); + var serviceClient = new ServiceClient(connection, restOpMock.Object); // This is required to cause onClose callback invocation. await connection.OpenAsync(TimeSpan.FromSeconds(1)).ConfigureAwait(false); serviceClient.Dispose(); @@ -110,7 +118,7 @@ public async Task CloseAsyncTest() // Instantiate AmqpServiceClient with Mock IHttpClientHelper and IotHubConnection var connection = new IotHubConnection(onCreate, onClose); - var serviceClient = new AmqpServiceClient(connection, restOpMock.Object); + var serviceClient = new ServiceClient(connection, restOpMock.Object); // This is required to cause onClose callback invocation. await connection.OpenAsync(TimeSpan.FromSeconds(1)).ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false);