From 04055220b784ae16b76dd79f4b187957b206b0aa Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 19 May 2021 13:31:44 -0700 Subject: [PATCH 01/77] feat(shared): Add common resources for convention-based operations --- .../devdoc/Convention-based operations.md | 211 ++++++++++++++++++ iothub/device/src/ClientOptions.cs | 15 +- iothub/device/src/PayloadCollection.cs | 160 +++++++++++++ shared/src/ConventionBasedConstants.cs | 41 ++++ shared/src/DefaultPayloadConvention.cs | 25 +++ shared/src/IWritablePropertyResponse.cs | 35 +++ .../src/Microsoft.Azure.Devices.Shared.csproj | 5 + shared/src/NewtonsoftJsonPayloadSerializer.cs | 71 ++++++ .../NewtonsoftJsonWritablePropertyResponse.cs | 56 +++++ shared/src/PayloadConvention.cs | 38 ++++ shared/src/PayloadEncoder.cs | 32 +++ shared/src/PayloadSerializer.cs | 75 +++++++ shared/src/SystemTextJsonPayloadSerializer.cs | 71 ++++++ .../SystemTextJsonWritablePropertyResponse.cs | 60 +++++ shared/src/Utf8PayloadEncoder.cs | 27 +++ 15 files changed, 921 insertions(+), 1 deletion(-) create mode 100644 iothub/device/devdoc/Convention-based operations.md create mode 100644 iothub/device/src/PayloadCollection.cs create mode 100644 shared/src/ConventionBasedConstants.cs create mode 100644 shared/src/DefaultPayloadConvention.cs create mode 100644 shared/src/IWritablePropertyResponse.cs create mode 100644 shared/src/NewtonsoftJsonPayloadSerializer.cs create mode 100644 shared/src/NewtonsoftJsonWritablePropertyResponse.cs create mode 100644 shared/src/PayloadConvention.cs create mode 100644 shared/src/PayloadEncoder.cs create mode 100644 shared/src/PayloadSerializer.cs create mode 100644 shared/src/SystemTextJsonPayloadSerializer.cs create mode 100644 shared/src/SystemTextJsonWritablePropertyResponse.cs create mode 100644 shared/src/Utf8PayloadEncoder.cs diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md new file mode 100644 index 0000000000..1af4489ab9 --- /dev/null +++ b/iothub/device/devdoc/Convention-based operations.md @@ -0,0 +1,211 @@ +## Plug and Play convention compatible APIs + +#### Common + +```csharp + +public abstract class PayloadConvention { + protected PayloadConvention(); + public abstract PayloadEncoder PayloadEncoder { get; } + public abstract PayloadSerializer PayloadSerializer { get; } + public virtual byte[] GetObjectBytes(object objectToSendWithConvention); +} + +public abstract class PayloadEncoder { + protected PayloadEncoder(); + public abstract Encoding ContentEncoding { get; } + public abstract byte[] EncodeStringToByteArray(string contentPayload); +} + +public abstract class PayloadSerializer { + protected PayloadSerializer(); + public abstract string ContentType { get; } + public abstract T ConvertFromObject(object objectToConvert); + public abstract IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null); + public abstract T DeserializeToType(string stringToDeserialize); + public abstract string SerializeToString(object objectToSerialize); + public abstract bool TryGetNestedObjectValue(object objectToConvert, string propertyName, out T outValue); +} + +public sealed class DefaultPayloadConvention : PayloadConvention { + public static readonly DefaultPayloadConvention Instance; + public DefaultPayloadConvention(); + public override PayloadEncoder PayloadEncoder { get; } + public override PayloadSerializer PayloadSerializer { get; } +} + +public class Utf8PayloadEncoder : PayloadEncoder { + public static readonly Utf8PayloadEncoder Instance; + public Utf8PayloadEncoder(); + public override Encoding ContentEncoding { get; } + public override byte[] EncodeStringToByteArray(string contentPayload); +} + +public class NewtonsoftJsonPayloadSerializer : PayloadSerializer { + public static readonly NewtonsoftJsonPayloadSerializer Instance; + public NewtonsoftJsonPayloadSerializer(); + public override string ContentType { get; } + public override T ConvertFromObject(object objectToConvert); + public override IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null); + public override T DeserializeToType(string stringToDeserialize); + public override string SerializeToString(object objectToSerialize); + public override bool TryGetNestedObjectValue(object objectToConvert, string propertyName, out T outValue); +} + +public abstract class PayloadCollection : IEnumerable, IEnumerable { + protected PayloadCollection(); + public IDictionary Collection { get; private set; } + public PayloadConvention Convention { get; internal set; } + public virtual object this[string key] { get; set; } + public virtual void Add(string key, object value); + public virtual void AddOrUpdate(string key, object value); + public bool Contains(string key); + public IEnumerator GetEnumerator(); + public virtual byte[] GetPayloadObjectBytes(); + public virtual string GetSerializedString(); + protected void SetCollection(PayloadCollection payloadCollection); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + public bool TryGetValue(string key, out T value); +} + +public static class ConventionBasedConstants { + public const string AckCodePropertyName = "ac"; + public const string AckDescriptionPropertyName = "ad"; + public const string AckVersionPropertyName = "av"; + public const string ComponentIdentifierKey = "__t"; + public const string ComponentIdentifierValue = "c"; + public const string ValuePropertyName = "value"; +} +``` + +### Properties + +```csharp +/// +/// Retrieve the device properties. +/// +/// A cancellation token to cancel the operation. +/// The device properties. +public Task GetClientPropertiesAsync(CancellationToken cancellationToken = default); + +/// +/// Update properties. +/// +/// Reported properties to push. +/// A cancellation token to cancel the operation. +/// The response containing the operation request Id and updated version no. +public Task UpdateClientPropertiesAsync(ClientPropertyCollection propertyCollection, CancellationToken cancellationToken = default); + +/// +/// Sets the global listener for Writable properties +/// +/// The global call back to handle all writable property updates. +/// Generic parameter to be interpreted by the client code. +/// A cancellation token to cancel the operation. +public Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken = default); +``` + +#### All related types + +```csharp +public class ClientProperties : ClientPropertyCollection { + public ClientPropertyCollection Writable { get; private set; } +} + +public class ClientPropertyCollection : PayloadCollection { + public ClientPropertyCollection(); + public long Version { get; protected set; } + public void Add(IDictionary properties, string componentName = null); + public override void Add(string propertyName, object propertyValue); + public void Add(string propertyName, object propertyValue, int statusCode, long version, string description = null, string componentName = null); + public void Add(string propertyName, object propertyValue, string componentName); + public void AddOrUpdate(IDictionary properties, string componentNam = null); + public override void AddOrUpdate(string propertyName, object propertyValue); + public void AddOrUpdate(string propertyName, object propertyValue, int statusCode, long version, string description = null, string componentName = null); + public void AddOrUpdate(string propertyName, object propertyValue, string componentName); + public bool Contains(string componentName, string propertyName); + public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue); +} + +public interface IWritablePropertyResponse { + int AckCode { get; set; } + string AckDescription { get; set; } + long AckVersion { get; set; } + object Value { get; set; } +} + +public sealed class NewtonsoftJsonWritablePropertyResponse : IWritablePropertyResponse { + public NewtonsoftJsonWritablePropertyResponse(object propertyValue, int ackCode, long ackVersion, string ackDescription = null); + public int AckCode { get; set; } + public string AckDescription { get; set; } + public long AckVersion { get; set; } + public object Value { get; set; } +} + +public class ClientPropertiesUpdateResponse { + public ClientPropertiesUpdateResponse(); + public string RequestId { get; internal set; } + public long Version { get; internal set; } +} +``` + +### Telemetry + +```csharp +/// +/// Send telemetry using the specified message. +/// +/// +/// Use the constructor to pass in the optional +/// that specifies your payload and serialization and encoding rules. +/// +/// The telemetry message. +/// A cancellation token to cancel the operation. +public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default); +``` +#### All related types + +```csharp +public class TelemetryCollection : PayloadCollection { + public TelemetryCollection(); + public override void Add(string telemetryName, object telemetryValue); + public override void AddOrUpdate(string telemetryName, object telemetryValue); +} + +public class TelemetryMessage : Message { + public TelemetryMessage(string componentName = null); + public new string ContentEncoding { get; internal set; } + public new string ContentType { get; internal set; } + public TelemetryCollection Telemetry { get; set; } + public override Stream GetBodyStream(); +} +``` + +### Commands + +```csharp +/// +/// Set the global command callback handler. +/// +/// A method implementation that will handle the incoming command. +/// Generic parameter to be interpreted by the client code. +/// A cancellation token to cancel the operation. +public Task SubscribeToCommandsAsync(Func> callback, object userContext, CancellationToken cancellationToken = default); +``` +#### All related types + +```csharp +public sealed class CommandRequest { + public string CommandName { get; private set; } + public string ComponentName { get; private set; } + public string DataAsJson { get; } + public T GetData(); +} + +public sealed class CommandResponse { + public CommandResponse(int status); + public CommandResponse(object result, int status); + public string ResultAsJson { get; } + public int Status { get; private set; } +} +``` diff --git a/iothub/device/src/ClientOptions.cs b/iothub/device/src/ClientOptions.cs index 8f6fd9cd0e..47d43a2ddf 100644 --- a/iothub/device/src/ClientOptions.cs +++ b/iothub/device/src/ClientOptions.cs @@ -19,7 +19,7 @@ public class ClientOptions /// /// The transport settings to use for all file upload operations, regardless of what protocol the device - /// client is configured with. All file upload operations take place over https. + /// client is configured with. All file upload operations take place over https. /// If FileUploadTransportSettings is not provided, then file upload operations will use the client certificates configured /// in the transport settings set for the non-file upload operations. /// @@ -54,5 +54,18 @@ public class ClientOptions /// or the flow. /// public int SasTokenRenewalBuffer { get; set; } + + /// + /// The payload convention to be used to serialize and encode the messages for convention based methods. + /// + /// + /// The defines both the serializer and encoding to be used for convention based messages. + /// You will only need to set this if you have objects that have special serialization rules or require a specific byte encoding. + /// + /// The default value is set to which uses the serializer + /// and encoder. + /// + /// + public PayloadConvention PayloadConvention { get; set; } = DefaultPayloadConvention.Instance; } } diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs new file mode 100644 index 0000000000..b417db2ee6 --- /dev/null +++ b/iothub/device/src/PayloadCollection.cs @@ -0,0 +1,160 @@ +// 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; +using System.Collections.Generic; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// The base class for all payloads that accept a . + /// + /// + /// This classes uses the and + /// based by default. + /// + public abstract class PayloadCollection : IEnumerable + { + /// + /// The underlying collection for the payload. + /// + public IDictionary Collection { get; private set; } = new Dictionary(); + + /// + /// The convention to use with this payload. + /// + public PayloadConvention Convention { get; internal set; } + + /// + /// Get the value at the specified key. + /// + /// + /// This accessor is best used to access and cast to simple types. + /// It is recommended to use to deserialize to a complex type. + /// + /// Key of value. + /// The specified property. + public virtual object this[string key] + { + get => Collection[key]; + set => AddOrUpdate(key, value); + } + + /// + /// Adds the key-value pair to the collection. + /// + /// + /// + /// + /// An element with the same key already exists in the collection. + public virtual void Add(string key, object value) + { + Collection.Add(key, value); + } + + /// + /// Adds or updates the key-value pair to the collection. + /// + /// The name of the telemetry. + /// The value of the telemetry. + /// is null. + public virtual void AddOrUpdate(string key, object value) + { + Collection[key] = value; + } + + /// + /// Gets the collection as a byte array. + /// + /// + /// This will get the fully encoded serialized string using both . + /// and methods implemented in the . + /// + /// A fully encoded serialized string. + public virtual byte[] GetPayloadObjectBytes() + { + return Convention.GetObjectBytes(Collection); + } + + /// + /// Determines whether the specified property is present. + /// + /// The key in the collection to locate. + /// true if the specified property is present; otherwise, false. + public bool Contains(string key) + { + return Collection.ContainsKey(key); + } + + /// + /// Gets the value of the object from the collection. + /// + /// + /// This class is used for both sending and receiving properties for the device. + /// + /// The type to cast the object to. + /// The key of the property to get. + /// The value of the object from the collection. + /// True if the collection contains an element with the specified key; otherwise, it returns false. + public bool TryGetValue(string key, out T value) + { + if (Collection.ContainsKey(key)) + { + // If the object is of type T go ahead and return it. + if (Collection[key] is T valueRef) + { + value = valueRef; + return true; + } + // If it's not we need to try to convert it using the serializer. + // JObject or JsonElement + value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); + return true; + } + + value = default; + return false; + } + + /// + /// Returns a serialized string of this collection from the method. + /// + /// A serialized string of this collection. + public virtual string GetSerializedString() + { + return Convention.PayloadSerializer.SerializeToString(Collection); + } + + /// + public IEnumerator GetEnumerator() + { + foreach (object property in Collection) + { + yield return property; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Will set the underlying of the payload collection. + /// + /// The collection to get the underlying dictionary from. + protected void SetCollection(PayloadCollection payloadCollection) + { + if (payloadCollection == null) + { + throw new ArgumentNullException(); + } + + Collection = payloadCollection.Collection; + Convention = payloadCollection.Convention; + } + } +} diff --git a/shared/src/ConventionBasedConstants.cs b/shared/src/ConventionBasedConstants.cs new file mode 100644 index 0000000000..1d4649fc5e --- /dev/null +++ b/shared/src/ConventionBasedConstants.cs @@ -0,0 +1,41 @@ +// 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.Shared +{ + /// + /// Container for common convention based constants. + /// + public static class ConventionBasedConstants + { + /// + /// Marker key to indicate a component-level property. + /// + public const string ComponentIdentifierKey = "__t"; + + /// + /// Marker value to indicate a component-level property. + /// + public const string ComponentIdentifierValue = "c"; + + /// + /// Represents the JSON document property name for the value of a writable property response. + /// + public const string ValuePropertyName = "value"; + + /// + /// Represents the JSON document property name for the Ack Code of a writable property response. + /// + public const string AckCodePropertyName = "ac"; + + /// + /// Represents the JSON document property name for the Ack Version of a writable property response. + /// + public const string AckVersionPropertyName = "av"; + + /// + /// Represents the JSON document property name for the Ack Description of a writable property response. + /// + public const string AckDescriptionPropertyName = "ad"; + } +} diff --git a/shared/src/DefaultPayloadConvention.cs b/shared/src/DefaultPayloadConvention.cs new file mode 100644 index 0000000000..749fb31a13 --- /dev/null +++ b/shared/src/DefaultPayloadConvention.cs @@ -0,0 +1,25 @@ +// 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.Shared +{ + /// + /// The default implementation of the class. + /// + /// + /// This class makes use of the serializer and the . + /// + public sealed class DefaultPayloadConvention : PayloadConvention + { + /// + /// A static instance of this class. + /// + public static readonly DefaultPayloadConvention Instance = new DefaultPayloadConvention(); + + /// + public override PayloadSerializer PayloadSerializer { get; } = NewtonsoftJsonPayloadSerializer.Instance; + + /// + public override PayloadEncoder PayloadEncoder { get; } = Utf8PayloadEncoder.Instance; + } +} diff --git a/shared/src/IWritablePropertyResponse.cs b/shared/src/IWritablePropertyResponse.cs new file mode 100644 index 0000000000..aea4986e4d --- /dev/null +++ b/shared/src/IWritablePropertyResponse.cs @@ -0,0 +1,35 @@ +// 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.Shared +{ + /// + /// The interface that defines the structure of a writable property response. + /// + /// + /// This interface is used to allow extension to use a different set of attributes for serialization. + /// For example our default implementation found in is based on serializer attributes. + /// + public interface IWritablePropertyResponse + { + /// + /// The unserialized property value. + /// + public object Value { get; set; } + + /// + /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. + /// + public int AckCode { get; set; } + + /// + /// The acknowledgment version, as supplied in the property update request. + /// + public long AckVersion { get; set; } + + /// + /// The acknowledgment description, an optional, human-readable message about the result of the property update. + /// + public string AckDescription { get; set; } + } +} diff --git a/shared/src/Microsoft.Azure.Devices.Shared.csproj b/shared/src/Microsoft.Azure.Devices.Shared.csproj index c62a779b83..28b2d2afa3 100644 --- a/shared/src/Microsoft.Azure.Devices.Shared.csproj +++ b/shared/src/Microsoft.Azure.Devices.Shared.csproj @@ -43,6 +43,11 @@ + + + + + diff --git a/shared/src/NewtonsoftJsonPayloadSerializer.cs b/shared/src/NewtonsoftJsonPayloadSerializer.cs new file mode 100644 index 0000000000..ec0b10cef0 --- /dev/null +++ b/shared/src/NewtonsoftJsonPayloadSerializer.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// A implementation. + /// + public class NewtonsoftJsonPayloadSerializer : PayloadSerializer + { + /// + /// The content type string. + /// + internal const string ApplicationJson = "application/json"; + + /// + /// The default instance of this class. + /// + public static readonly NewtonsoftJsonPayloadSerializer Instance = new NewtonsoftJsonPayloadSerializer(); + + /// + public override string ContentType => ApplicationJson; + + /// + public override string SerializeToString(object objectToSerialize) + { + return JsonConvert.SerializeObject(objectToSerialize); + } + + /// + public override T DeserializeToType(string stringToDeserialize) + { + return JsonConvert.DeserializeObject(stringToDeserialize); + } + + /// + public override T ConvertFromObject(object objectToConvert) + { + if (objectToConvert == null) + { + return default; + } + return ((JObject)objectToConvert).ToObject(); + } + + /// + public override bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue) + { + outValue = default; + if (nestedObject == null || string.IsNullOrEmpty(propertyName)) + { + return false; + } + if (((JObject)nestedObject).TryGetValue(propertyName, out JToken element)) + { + outValue = element.ToObject(); + return true; + } + return false; + } + + /// + public override IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null) + { + return new NewtonsoftJsonWritablePropertyResponse(value, statusCode, version, description); + } + } +} diff --git a/shared/src/NewtonsoftJsonWritablePropertyResponse.cs b/shared/src/NewtonsoftJsonWritablePropertyResponse.cs new file mode 100644 index 0000000000..348d725edd --- /dev/null +++ b/shared/src/NewtonsoftJsonWritablePropertyResponse.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// An optional, helper class for constructing a writable property response. + /// + /// + /// This helper class will only work with . + /// It uses based to define the JSON property names. + /// + public sealed class NewtonsoftJsonWritablePropertyResponse : IWritablePropertyResponse + { + /// + /// Convenience constructor for specifying the properties. + /// + /// The unserialized property value. + /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. + /// The acknowledgment version, as supplied in the property update request. + /// The acknowledgment description, an optional, human-readable message about the result of the property update. + public NewtonsoftJsonWritablePropertyResponse(object propertyValue, int ackCode, long ackVersion, string ackDescription = default) + { + Value = propertyValue; + AckCode = ackCode; + AckVersion = ackVersion; + AckDescription = ackDescription; + } + + /// + /// The unserialized property value. + /// + [JsonProperty(ConventionBasedConstants.ValuePropertyName)] + public object Value { get; set; } + + /// + /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. + /// + [JsonProperty(ConventionBasedConstants.AckCodePropertyName)] + public int AckCode { get; set; } + + /// + /// The acknowledgment version, as supplied in the property update request. + /// + [JsonProperty(ConventionBasedConstants.AckVersionPropertyName)] + public long AckVersion { get; set; } + + /// + /// The acknowledgment description, an optional, human-readable message about the result of the property update. + /// + [JsonProperty(ConventionBasedConstants.AckDescriptionPropertyName, DefaultValueHandling = DefaultValueHandling.Ignore)] + public string AckDescription { get; set; } + } +} diff --git a/shared/src/PayloadConvention.cs b/shared/src/PayloadConvention.cs new file mode 100644 index 0000000000..99fd943723 --- /dev/null +++ b/shared/src/PayloadConvention.cs @@ -0,0 +1,38 @@ +// 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.Shared +{ + /// + /// The payload convention class. + /// + /// The payload convention is used to define a specific serializer as well as a specific content encoding. + /// For example, IoT has a convention that is designed + /// to make it easier to get started with products that use specific conventions by default. + public abstract class PayloadConvention + { + /// + /// Gets the serializer used for the payload. + /// + /// A serializer that will be used to convert the payload object to a string. + public abstract PayloadSerializer PayloadSerializer { get; } + + /// + /// Gets the encoder used for the payload to be serialized. + /// + /// An encoder that will be used to convert the serialized string to a byte array. + public abstract PayloadEncoder PayloadEncoder { get; } + + /// + /// Returns the byte array for the convention-based message. + /// + /// This base class will use the and to create this byte array. + /// The convention-based message that is to be sent. + /// The correctly encoded object for this convention. + public virtual byte[] GetObjectBytes(object objectToSendWithConvention) + { + string serializedString = PayloadSerializer.SerializeToString(objectToSendWithConvention); + return PayloadEncoder.EncodeStringToByteArray(serializedString); + } + } +} diff --git a/shared/src/PayloadEncoder.cs b/shared/src/PayloadEncoder.cs new file mode 100644 index 0000000000..03b5c8bb72 --- /dev/null +++ b/shared/src/PayloadEncoder.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// This class specifies the byte encoding for the payload. + /// + /// + /// The encoder is responsible for encoding all of your objects into the correct bytes for the that uses it. + /// + /// By default we have implemented the class that uses + /// to handle the encoding for the class. + /// + /// + public abstract class PayloadEncoder + { + /// + /// The used for the payload. + /// + public abstract Encoding ContentEncoding { get; } + + /// + /// Outputs an encoded byte array for the specified payload string. + /// + /// The contents of the message payload. + /// An encoded byte array. + public abstract byte[] EncodeStringToByteArray(string contentPayload); + } +} diff --git a/shared/src/PayloadSerializer.cs b/shared/src/PayloadSerializer.cs new file mode 100644 index 0000000000..9fced3233e --- /dev/null +++ b/shared/src/PayloadSerializer.cs @@ -0,0 +1,75 @@ +// 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.Shared +{ + /// + /// Provides the serialization for a specified convention. + /// + /// + /// The serializer is responsible for converting all of your objects into the correct format for the that uses it. + /// + /// By default we have implemented the class that uses + /// to handle the serialization for the class. + /// + /// + public abstract class PayloadSerializer + { + /// + /// Used to specify what type of content to expect. + /// + /// This can be free-form but should adhere to standard MIME types. For example, "application/json" is what we implement by default. + /// A string representing the content type to use when sending a payload. + public abstract string ContentType { get; } + + /// + /// Serialize the specified object to a string. + /// + /// Object to serialize. + /// A serialized string of the object. + public abstract string SerializeToString(object objectToSerialize); + + /// + /// Convert the serialized string to an object. + /// + /// The type you want to return. + /// String to deserialize. + /// A fully deserialized type. + public abstract T DeserializeToType(string stringToDeserialize); + + /// + /// Converts the object using the serializer. + /// + /// This class is used by the PayloadCollection-based classes to attempt to convert from the native serializer type + /// (for example, JObject or JsonElement) to the desired type. + /// When you implement this you need to be aware of what type your serializer will use for anonymous types. + /// The type to convert to. + /// The object to convert. + /// A converted object + public abstract T ConvertFromObject(object objectToConvert); + + /// + /// Gets a nested property from the serialized data. + /// + /// + /// This is used internally by our PayloadCollection-based classes to attempt to get a property of the underlying object. + /// An example of this would be a property under the component. + /// + /// The type to convert the retrieved property to. + /// The object that might contain the nested property. + /// The name of the property to be retrieved. + /// True if the nested object contains an element with the specified key; otherwise, it returns false. + /// + public abstract bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue); + + /// + /// Creates the correct to be used with this serializer. + /// + /// The value of the property. + /// The status code of the write operation. + /// The version the property is responding to. + /// An optional description of the writable property response. + /// The writable property response to be used with this serializer. + public abstract IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = default); + } +} diff --git a/shared/src/SystemTextJsonPayloadSerializer.cs b/shared/src/SystemTextJsonPayloadSerializer.cs new file mode 100644 index 0000000000..8a5ab3f235 --- /dev/null +++ b/shared/src/SystemTextJsonPayloadSerializer.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NET451 + +using System.Text.Json; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + /// + /// A implementation. + /// + public class SystemTextJsonPayloadSerializer : PayloadSerializer + { + /// + /// The Content Type string. + /// + internal const string ApplicationJson = "application/json"; + + /// + /// The default instance of this class. + /// + public static readonly SystemTextJsonPayloadSerializer Instance = new SystemTextJsonPayloadSerializer(); + + /// + public override string ContentType => ApplicationJson; + + /// + public override string SerializeToString(object objectToSerialize) + { + return JsonSerializer.Serialize(objectToSerialize); + } + + /// + public override T DeserializeToType(string stringToDeserialize) + { + return JsonSerializer.Deserialize(stringToDeserialize); + } + + /// + public override T ConvertFromObject(object objectToConvert) + { + return DeserializeToType(((JsonElement)objectToConvert).ToString()); + } + + /// + public override bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue) + { + outValue = default; + if (nestedObject == null || string.IsNullOrEmpty(propertyName)) + { + return false; + } + if (((JsonElement)nestedObject).TryGetProperty(propertyName, out JsonElement element)) + { + outValue = DeserializeToType(element.GetRawText()); + return true; + } + return false; + } + + /// + public override IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null) + { + return new SystemTextJsonWritablePropertyResponse(value, statusCode, version, description); + } + } +} + +#endif diff --git a/shared/src/SystemTextJsonWritablePropertyResponse.cs b/shared/src/SystemTextJsonWritablePropertyResponse.cs new file mode 100644 index 0000000000..8873736908 --- /dev/null +++ b/shared/src/SystemTextJsonWritablePropertyResponse.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NET451 + +using System.Text.Json.Serialization; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// An optional, helper class for constructing a writable property response. + /// + /// + /// This helper class will only work with . + /// It uses based to define the JSON property names. + /// + public sealed class SystemTextJsonWritablePropertyResponse : IWritablePropertyResponse + { + /// + /// Convenience constructor for specifying the properties. + /// + /// The unserialized property value. + /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. + /// The acknowledgment version, as supplied in the property update request. + /// The acknowledgment description, an optional, human-readable message about the result of the property update. + public SystemTextJsonWritablePropertyResponse(object propertyValue, int ackCode, long ackVersion, string ackDescription = default) + { + Value = propertyValue; + AckCode = ackCode; + AckVersion = ackVersion; + AckDescription = ackDescription; + } + + /// + /// The unserialized property value. + /// + [JsonPropertyName(ConventionBasedConstants.ValuePropertyName)] + public object Value { get; set; } + + /// + /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. + /// + [JsonPropertyName(ConventionBasedConstants.AckCodePropertyName)] + public int AckCode { get; set; } + + /// + /// The acknowledgment version, as supplied in the property update request. + /// + [JsonPropertyName(ConventionBasedConstants.AckVersionPropertyName)] + public long AckVersion { get; set; } + + /// + /// The acknowledgment description, an optional, human-readable message about the result of the property update. + /// + [JsonPropertyName(ConventionBasedConstants.AckDescriptionPropertyName)] + public string AckDescription { get; set; } + } +} + +#endif diff --git a/shared/src/Utf8PayloadEncoder.cs b/shared/src/Utf8PayloadEncoder.cs new file mode 100644 index 0000000000..0330c3fcf2 --- /dev/null +++ b/shared/src/Utf8PayloadEncoder.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// A UTF-8 implementation. + /// + public class Utf8PayloadEncoder : PayloadEncoder + { + /// + /// The default instance of this class. + /// + public static readonly Utf8PayloadEncoder Instance = new Utf8PayloadEncoder(); + + /// + public override Encoding ContentEncoding => Encoding.UTF8; + + /// + public override byte[] EncodeStringToByteArray(string contentPayload) + { + return ContentEncoding.GetBytes(contentPayload); + } + } +} From 3bdd81440cf4c839859612ac345ab7aadcd6c24a Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 18 May 2021 14:16:16 -0700 Subject: [PATCH 02/77] feat(iot-device): Add support for convention-based telemetry operation --- iothub/device/src/ClientOptions.cs | 4 +- .../DeviceClient.ConventionBasedOperations.cs | 30 ++ iothub/device/src/DeviceClient.cs | 2 +- iothub/device/src/IDelegatingHandler.cs | 4 +- ...nternalClient.ConventionBasedOperations.cs | 36 ++ iothub/device/src/InternalClient.cs | 6 +- iothub/device/src/IotHubClientDiagnostic.cs | 4 +- iothub/device/src/Message.cs | 379 +-------------- iothub/device/src/MessageBase.cs | 447 ++++++++++++++++++ .../ModuleClient.ConventionBasedOperations.cs | 30 ++ iothub/device/src/ModuleClient.cs | 2 +- iothub/device/src/TelemetryCollection.cs | 37 ++ iothub/device/src/TelemetryMessage.cs | 45 ++ .../Transport/Amqp/AmqpTransportHandler.cs | 4 +- iothub/device/src/Transport/Amqp/AmqpUnit.cs | 8 +- .../AmqpIot/AmqpIotMessageConverter.cs | 6 +- .../Transport/AmqpIot/AmqpIotSendingLink.cs | 4 +- .../src/Transport/DefaultDelegatingHandler.cs | 4 +- .../src/Transport/ErrorDelegatingHandler.cs | 4 +- .../src/Transport/HttpTransportHandler.cs | 8 +- .../Transport/Mqtt/MqttTransportHandler.cs | 4 +- .../src/Transport/RetryDelegatingHandler.cs | 4 +- .../tests/Mqtt/MqttIotHubAdapterTest.cs | 10 +- 23 files changed, 675 insertions(+), 407 deletions(-) create mode 100644 iothub/device/src/DeviceClient.ConventionBasedOperations.cs create mode 100644 iothub/device/src/InternalClient.ConventionBasedOperations.cs create mode 100644 iothub/device/src/MessageBase.cs create mode 100644 iothub/device/src/ModuleClient.ConventionBasedOperations.cs create mode 100644 iothub/device/src/TelemetryCollection.cs create mode 100644 iothub/device/src/TelemetryMessage.cs diff --git a/iothub/device/src/ClientOptions.cs b/iothub/device/src/ClientOptions.cs index 47d43a2ddf..d1ea0c7546 100644 --- a/iothub/device/src/ClientOptions.cs +++ b/iothub/device/src/ClientOptions.cs @@ -26,8 +26,8 @@ public class ClientOptions public Http1TransportSettings FileUploadTransportSettings { get; set; } = new Http1TransportSettings(); /// - /// The configuration for setting for every message sent by the device or module client instance. - /// The default behavior is that is set only by the user. + /// The configuration for setting for every message sent by the device or module client instance. + /// The default behavior is that is set only by the user. /// public SdkAssignsMessageId SdkAssignsMessageId { get; set; } = SdkAssignsMessageId.Never; diff --git a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs new file mode 100644 index 0000000000..70db59d4fd --- /dev/null +++ b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs @@ -0,0 +1,30 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// Contains methods that a convention based device can use to send telemetry to the service, + /// respond to commands and perform operations on its properties. + /// + /// + public partial class DeviceClient : IDisposable + { + /// + /// Send telemetry using the specified message. + /// + /// + /// Use the constructor to pass in the optional component name + /// that the telemetry message is from. + /// + /// The telemetry message. + /// A cancellation token to cancel the operation. + /// + public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default) + => InternalClient.SendTelemetryAsync(telemetryMessage, cancellationToken); + } +} diff --git a/iothub/device/src/DeviceClient.cs b/iothub/device/src/DeviceClient.cs index d6179291ce..0bc6d5d1fc 100644 --- a/iothub/device/src/DeviceClient.cs +++ b/iothub/device/src/DeviceClient.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Devices.Client /// Contains methods that a device can use to send messages to and receive from the service. /// /// - public class DeviceClient : IDisposable + public partial class DeviceClient : IDisposable { /// /// Default operation timeout. diff --git a/iothub/device/src/IDelegatingHandler.cs b/iothub/device/src/IDelegatingHandler.cs index 2df13cfa81..7b8cfa8918 100644 --- a/iothub/device/src/IDelegatingHandler.cs +++ b/iothub/device/src/IDelegatingHandler.cs @@ -23,9 +23,9 @@ internal interface IDelegatingHandler : IContinuationProvider messages, CancellationToken cancellationToken); + Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken); // Telemetry downlink for devices. Task ReceiveAsync(CancellationToken cancellationToken); diff --git a/iothub/device/src/InternalClient.ConventionBasedOperations.cs b/iothub/device/src/InternalClient.ConventionBasedOperations.cs new file mode 100644 index 0000000000..4cd5607bd1 --- /dev/null +++ b/iothub/device/src/InternalClient.ConventionBasedOperations.cs @@ -0,0 +1,36 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// Contains methods that a convention based device/ module can use to send telemetry to the service, + /// respond to commands and perform operations on its properties. + /// + internal partial class InternalClient + { + internal PayloadConvention PayloadConvention => _clientOptions.PayloadConvention ?? DefaultPayloadConvention.Instance; + + internal Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken) + { + if (telemetryMessage == null) + { + throw new ArgumentNullException(nameof(telemetryMessage)); + } + + if (telemetryMessage.Telemetry != null) + { + telemetryMessage.Telemetry.Convention = PayloadConvention; + telemetryMessage.PayloadContentEncoding = PayloadConvention.PayloadEncoder.ContentEncoding.WebName; + telemetryMessage.PayloadContentType = PayloadConvention.PayloadSerializer.ContentType; + } + + return SendEventAsync(telemetryMessage, cancellationToken); + } + } +} diff --git a/iothub/device/src/InternalClient.cs b/iothub/device/src/InternalClient.cs index 5f4794c9c9..32d8df73c8 100644 --- a/iothub/device/src/InternalClient.cs +++ b/iothub/device/src/InternalClient.cs @@ -105,7 +105,7 @@ public enum MessageResponse /// Contains methods that a device can use to send messages to and receive messages from the service, /// respond to direct method invocations from the service, and send and receive twin property updates. /// - internal class InternalClient : IDisposable + internal partial class InternalClient : IDisposable { private readonly SemaphoreSlim _methodsSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _deviceReceiveMessageSemaphore = new SemaphoreSlim(1, 1); @@ -643,7 +643,7 @@ public Task RejectAsync(Message message, CancellationToken cancellationToken) /// Sends an event to device hub /// /// The message containing the event - public async Task SendEventAsync(Message message) + public async Task SendEventAsync(MessageBase message) { try { @@ -662,7 +662,7 @@ public async Task SendEventAsync(Message message) /// Sends an event to device hub /// /// The message containing the event - public Task SendEventAsync(Message message, CancellationToken cancellationToken) + public Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { if (message == null) { diff --git a/iothub/device/src/IotHubClientDiagnostic.cs b/iothub/device/src/IotHubClientDiagnostic.cs index 4ed50de462..0917efc570 100644 --- a/iothub/device/src/IotHubClientDiagnostic.cs +++ b/iothub/device/src/IotHubClientDiagnostic.cs @@ -12,7 +12,7 @@ internal class IotHubClientDiagnostic private const string DiagnosticCreationTimeUtcKey = "creationtimeutc"; private static readonly DateTime Dt1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - internal static bool AddDiagnosticInfoIfNecessary(Message message, int diagnosticSamplingPercentage, ref int currentMessageCount) + internal static bool AddDiagnosticInfoIfNecessary(MessageBase message, int diagnosticSamplingPercentage, ref int currentMessageCount) { bool result = false; @@ -27,7 +27,7 @@ internal static bool AddDiagnosticInfoIfNecessary(Message message, int diagnosti return result; } - internal static bool HasDiagnosticProperties(Message message) + internal static bool HasDiagnosticProperties(MessageBase message) { return message.SystemProperties.ContainsKey(MessageSystemPropertyNames.DiagId) && message.SystemProperties.ContainsKey(MessageSystemPropertyNames.DiagCorrelationContext); } diff --git a/iothub/device/src/Message.cs b/iothub/device/src/Message.cs index 2130074304..1c5fb54f81 100644 --- a/iothub/device/src/Message.cs +++ b/iothub/device/src/Message.cs @@ -1,12 +1,7 @@ // 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.IO; -using System.Threading; -using Microsoft.Azure.Devices.Client.Extensions; -using Microsoft.Azure.Devices.Client.Common.Api; -using System.Collections.Generic; using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client @@ -14,26 +9,14 @@ namespace Microsoft.Azure.Devices.Client /// /// The data structure represent the message that is used for interacting with IotHub. /// - public sealed class Message : IReadOnlyIndicator, IDisposable + public sealed class Message : MessageBase { - private volatile Stream _bodyStream; - private bool _disposed; - private StreamDisposalResponsibility _streamDisposalResponsibility; - - private const long StreamCannotSeek = -1; - private long _originalStreamPosition = StreamCannotSeek; - - private int _getBodyCalled; - private long _sizeInBytesCalled; - /// /// Default constructor with no body data /// public Message() + : base() { - Properties = new ReadOnlyDictionary45(new Dictionary(StringComparer.OrdinalIgnoreCase), this); - SystemProperties = new ReadOnlyDictionary45(new Dictionary(StringComparer.OrdinalIgnoreCase), this); - InitializeWithStream(Stream.Null, StreamDisposalResponsibility.Sdk); } /// @@ -43,12 +26,8 @@ public Message() /// A stream which will be used as body stream. // UWP cannot expose a method with System.IO.Stream in signature. TODO: consider adding an IRandomAccessStream overload public Message(Stream stream) - : this() + : base(stream) { - if (stream != null) - { - InitializeWithStream(stream, StreamDisposalResponsibility.App); - } } /// @@ -57,171 +36,27 @@ public Message(Stream stream) /// User should treat the input byte array as immutable when sending the message. /// A byte array which will be used to form the body stream. public Message(byte[] byteArray) - : this(new MemoryStream(byteArray)) + : base(byteArray) { - // Reset the owning of the stream - _streamDisposalResponsibility = StreamDisposalResponsibility.Sdk; } /// - /// This constructor is only used on the Gateway HTTP path so that we can clean up the stream. + /// This constructor is only used on the Gateway HTTP path and AMQP SendEventAsync() so that we can clean up the stream. /// /// A stream which will be used as body stream. /// Indicates if the stream passed in should be disposed by the client library, or by the calling application. internal Message(Stream stream, StreamDisposalResponsibility streamDisposalResponsibility) - : this(stream) - { - _streamDisposalResponsibility = streamDisposalResponsibility; - } - - /// - /// [Required for two way requests] Used to correlate two-way communication. - /// Format: A case-sensitive string ( up to 128 char long) of ASCII 7-bit alphanumeric chars - /// + {'-', ':', '/', '\', '.', '+', '%', '_', '#', '*', '?', '!', '(', ')', ',', '=', '@', ';', '$', '''}. - /// Non-alphanumeric characters are from URN RFC. - /// - public string MessageId - { - get => GetSystemProperty(MessageSystemPropertyNames.MessageId); - set => SystemProperties[MessageSystemPropertyNames.MessageId] = value; - } - - /// - /// [Required] Destination of the message - /// - public string To - { - get => GetSystemProperty(MessageSystemPropertyNames.To); - set => SystemProperties[MessageSystemPropertyNames.To] = value; - } - - /// - /// [Optional] The time when this message is considered expired - /// - public DateTime ExpiryTimeUtc - { - get => GetSystemProperty(MessageSystemPropertyNames.ExpiryTimeUtc); - internal set => SystemProperties[MessageSystemPropertyNames.ExpiryTimeUtc] = value; - } - - /// - /// Used in message responses and feedback - /// - public string CorrelationId - { - get => GetSystemProperty(MessageSystemPropertyNames.CorrelationId); - set => SystemProperties[MessageSystemPropertyNames.CorrelationId] = value; - } - - /// - /// [Required] SequenceNumber of the received message - /// - public ulong SequenceNumber - { - get => GetSystemProperty(MessageSystemPropertyNames.SequenceNumber); - internal set => SystemProperties[MessageSystemPropertyNames.SequenceNumber] = value; - } - - /// - /// [Required] LockToken of the received message - /// - public string LockToken - { - get => GetSystemProperty(MessageSystemPropertyNames.LockToken); - internal set => SystemProperties[MessageSystemPropertyNames.LockToken] = value; - } - - /// - /// Date and time when the device-to-cloud message was received by the server. - /// - public DateTime EnqueuedTimeUtc - { - get => GetSystemProperty(MessageSystemPropertyNames.EnqueuedTime); - internal set => SystemProperties[MessageSystemPropertyNames.EnqueuedTime] = value; - } - - /// - /// Number of times the message has been previously delivered - /// - public uint DeliveryCount - { - get => GetSystemProperty(MessageSystemPropertyNames.DeliveryCount); - internal set => SystemProperties[MessageSystemPropertyNames.DeliveryCount] = (byte)value; - } - - /// - /// [Required in feedback messages] Used to specify the origin of messages generated by device hub. - /// Possible value: “{hub name}/” - /// - public string UserId - { - get => GetSystemProperty(MessageSystemPropertyNames.UserId); - set => SystemProperties[MessageSystemPropertyNames.UserId] = value; - } - - /// - /// For outgoing messages, contains the Mqtt topic that the message is being sent to - /// For incoming messages, contains the Mqtt topic that the message arrived on - /// - internal string MqttTopicName { get; set; } - - /// - /// Used to specify the schema of the message content. - /// - public string MessageSchema + : base(stream, streamDisposalResponsibility) { - get => GetSystemProperty(MessageSystemPropertyNames.MessageSchema); - set => SystemProperties[MessageSystemPropertyNames.MessageSchema] = value; } - /// - /// Custom date property set by the originator of the message. - /// - public DateTime CreationTimeUtc - { - get => GetSystemProperty(MessageSystemPropertyNames.CreationTimeUtc); - set => SystemProperties[MessageSystemPropertyNames.CreationTimeUtc] = value; - } - - /// - /// True if the message is set as a security message - /// - public bool IsSecurityMessage => CommonConstants.SecurityMessageInterfaceId.Equals(GetSystemProperty(MessageSystemPropertyNames.InterfaceId), StringComparison.Ordinal); - /// /// Used to specify the content type of the message. /// public string ContentType { - get => GetSystemProperty(MessageSystemPropertyNames.ContentType); - set => SystemProperties[MessageSystemPropertyNames.ContentType] = value; - } - - /// - /// Specifies the input name on which the message was sent, if there was one. - /// - public string InputName - { - get => GetSystemProperty(MessageSystemPropertyNames.InputName); - internal set => SystemProperties[MessageSystemPropertyNames.InputName] = value; - } - - /// - /// Specifies the device Id from which this message was sent, if there is one. - /// - public string ConnectionDeviceId - { - get => GetSystemProperty(MessageSystemPropertyNames.ConnectionDeviceId); - internal set => SystemProperties[MessageSystemPropertyNames.ConnectionDeviceId] = value; - } - - /// - /// Specifies the module Id from which this message was sent, if there is one. - /// - public string ConnectionModuleId - { - get => GetSystemProperty(MessageSystemPropertyNames.ConnectionModuleId); - internal set => SystemProperties[MessageSystemPropertyNames.ConnectionModuleId] = value; + get => PayloadContentType; + set => PayloadContentType = value; } /// @@ -229,202 +64,8 @@ public string ConnectionModuleId /// public string ContentEncoding { - get => GetSystemProperty(MessageSystemPropertyNames.ContentEncoding); - set => SystemProperties[MessageSystemPropertyNames.ContentEncoding] = value; - } - - /// - /// The DTDL component name from where the telemetry message has originated. - /// This is relevant only for plug and play certified devices. - /// - public string ComponentName - { - get => GetSystemProperty(MessageSystemPropertyNames.ComponentName); - set => SystemProperties[MessageSystemPropertyNames.ComponentName] = value; - } - - /// - /// Gets the dictionary of user properties which are set when user send the data. - /// - public IDictionary Properties { get; private set; } - - /// - /// Gets the dictionary of system properties which are managed internally. - /// - internal IDictionary SystemProperties { get; private set; } - - bool IReadOnlyIndicator.IsReadOnly => Interlocked.Read(ref _sizeInBytesCalled) == 1; - - - /// - /// The body stream of the current event data instance - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Naming", - "CA1721:Property names should not match get methods", - Justification = "Cannot remove public property on a public facing type")] - public Stream BodyStream => _bodyStream; - - /// - /// Gets or sets the deliveryTag which is used for server side check-pointing. - /// - internal ArraySegment DeliveryTag { get; set; } - - /// - /// Dispose the current event data instance - /// - public void Dispose() - { - Dispose(true); - } - - internal bool HasBodyStream() - { - return _bodyStream != null; - } - - /// - /// Return the body stream of the current event data instance - /// - /// - /// throws if the method has been called. - /// throws if the event data has already been disposed. - /// This method can only be called once and afterwards method will throw . - public Stream GetBodyStream() - { - ThrowIfDisposed(); - SetGetBodyCalled(); - return _bodyStream ?? Stream.Null; - } - - /// - /// This methods return the body stream as a byte array - /// - /// - /// throws if the event data has already been disposed. - public byte[] GetBytes() - { - ThrowIfDisposed(); - SetGetBodyCalled(); - if (_bodyStream == null) - { -#if NET451 - return new byte[] { }; -#else - return Array.Empty(); -#endif - } - - return ReadFullStream(_bodyStream); - } - - /// - /// Clones an existing instance and sets content body defined by on it. - /// - /// - /// The cloned message has the message as the original message. - /// User should treat the input byte array as immutable when sending the message. - /// - /// Message content to be set after clone. - /// A new instance of with body content defined by , - /// and user/system properties of the cloned instance. - /// - public Message CloneWithBody(in byte[] byteArray) - { - var result = new Message(byteArray); - - foreach (string key in Properties.Keys) - { - result.Properties.Add(key, Properties[key]); - } - - foreach (string key in SystemProperties.Keys) - { - result.SystemProperties.Add(key, SystemProperties[key]); - } - - return result; - } - - internal void ResetBody() - { - if (_originalStreamPosition == StreamCannotSeek) - { - throw new IOException("Stream cannot seek."); - } - - _bodyStream.Seek(_originalStreamPosition, SeekOrigin.Begin); - Interlocked.Exchange(ref _getBodyCalled, 0); - } - - internal bool IsBodyCalled => Volatile.Read(ref _getBodyCalled) == 1; - - private void SetGetBodyCalled() - { - if (1 == Interlocked.Exchange(ref _getBodyCalled, 1)) - { - throw Fx.Exception.AsError(new InvalidOperationException(ApiResources.MessageBodyConsumed)); - } - } - - /// - /// Sets the message as an security message - /// - public void SetAsSecurityMessage() - { - SystemProperties[MessageSystemPropertyNames.InterfaceId] = CommonConstants.SecurityMessageInterfaceId; - } - - private void InitializeWithStream(Stream stream, StreamDisposalResponsibility streamDisposalResponsibility) - { - // This method should only be used in constructor because - // this has no locking on the bodyStream. - _bodyStream = stream; - _streamDisposalResponsibility = streamDisposalResponsibility; - - if (_bodyStream.CanSeek) - { - _originalStreamPosition = _bodyStream.Position; - } - } - - private static byte[] ReadFullStream(Stream inputStream) - { - using var ms = new MemoryStream(); - inputStream.CopyTo(ms); - return ms.ToArray(); - } - - private T GetSystemProperty(string key) - { - return SystemProperties.ContainsKey(key) - ? (T)SystemProperties[key] - : default; - } - - internal void ThrowIfDisposed() - { - if (_disposed) - { - throw Fx.Exception.ObjectDisposed(ApiResources.MessageDisposed); - } - } - - private void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - if (_bodyStream != null && _streamDisposalResponsibility == StreamDisposalResponsibility.Sdk) - { - _bodyStream.Dispose(); - _bodyStream = null; - } - } - } - - _disposed = true; + get => PayloadContentEncoding; + set => PayloadContentEncoding = value; } } } diff --git a/iothub/device/src/MessageBase.cs b/iothub/device/src/MessageBase.cs new file mode 100644 index 0000000000..11c491cc27 --- /dev/null +++ b/iothub/device/src/MessageBase.cs @@ -0,0 +1,447 @@ +// 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.IO; +using System.Threading; +using Microsoft.Azure.Devices.Client.Common.Api; +using System.Collections.Generic; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// The base Message class that is used for interacting with IotHub. + /// + public abstract class MessageBase : IReadOnlyIndicator, IDisposable + { + private volatile Stream _bodyStream; + private bool _disposed; + private StreamDisposalResponsibility _streamDisposalResponsibility; + + private const long StreamCannotSeek = -1; + private long _originalStreamPosition = StreamCannotSeek; + + private int _getBodyCalled; + private long _sizeInBytesCalled; + + /// + /// Default constructor with no body data + /// + public MessageBase() + { + Properties = new ReadOnlyDictionary45(new Dictionary(StringComparer.OrdinalIgnoreCase), this); + SystemProperties = new ReadOnlyDictionary45(new Dictionary(StringComparer.OrdinalIgnoreCase), this); + InitializeWithStream(Stream.Null, StreamDisposalResponsibility.Sdk); + } + + /// + /// Constructor which uses the argument stream as the body stream. + /// + /// User is expected to own the disposing of the stream when using this constructor. + /// A stream which will be used as body stream. + // UWP cannot expose a method with System.IO.Stream in signature. TODO: consider adding an IRandomAccessStream overload + public MessageBase(Stream stream) + : this() + { + if (stream != null) + { + InitializeWithStream(stream, StreamDisposalResponsibility.App); + } + } + + /// + /// Constructor which uses the input byte array as the body. + /// + /// User should treat the input byte array as immutable when sending the message. + /// A byte array which will be used to form the body stream. + public MessageBase(byte[] byteArray) + : this(new MemoryStream(byteArray)) + { + // Reset the owning of the stream + _streamDisposalResponsibility = StreamDisposalResponsibility.Sdk; + } + + /// + /// This constructor is only used on the Gateway HTTP path and AMQP SendEventAsync()so that we can clean up the stream. + /// + /// A stream which will be used as body stream. + /// Indicates if the stream passed in should be disposed by the client library, or by the calling application. + internal MessageBase(Stream stream, StreamDisposalResponsibility streamDisposalResponsibility) + : this(stream) + { + _streamDisposalResponsibility = streamDisposalResponsibility; + } + + /// + /// [Required for two way requests] Used to correlate two-way communication. + /// Format: A case-sensitive string ( up to 128 char long) of ASCII 7-bit alphanumeric chars + /// + {'-', ':', '/', '\', '.', '+', '%', '_', '#', '*', '?', '!', '(', ')', ',', '=', '@', ';', '$', '''}. + /// Non-alphanumeric characters are from URN RFC. + /// + public string MessageId + { + get => GetSystemProperty(MessageSystemPropertyNames.MessageId); + set => SystemProperties[MessageSystemPropertyNames.MessageId] = value; + } + + /// + /// [Required] Destination of the message + /// + public string To + { + get => GetSystemProperty(MessageSystemPropertyNames.To); + set => SystemProperties[MessageSystemPropertyNames.To] = value; + } + + /// + /// [Optional] The time when this message is considered expired + /// + public DateTime ExpiryTimeUtc + { + get => GetSystemProperty(MessageSystemPropertyNames.ExpiryTimeUtc); + internal set => SystemProperties[MessageSystemPropertyNames.ExpiryTimeUtc] = value; + } + + /// + /// Used in message responses and feedback + /// + public string CorrelationId + { + get => GetSystemProperty(MessageSystemPropertyNames.CorrelationId); + set => SystemProperties[MessageSystemPropertyNames.CorrelationId] = value; + } + + /// + /// [Required] SequenceNumber of the received message + /// + public ulong SequenceNumber + { + get => GetSystemProperty(MessageSystemPropertyNames.SequenceNumber); + internal set => SystemProperties[MessageSystemPropertyNames.SequenceNumber] = value; + } + + /// + /// [Required] LockToken of the received message + /// + public string LockToken + { + get => GetSystemProperty(MessageSystemPropertyNames.LockToken); + internal set => SystemProperties[MessageSystemPropertyNames.LockToken] = value; + } + + /// + /// Date and time when the device-to-cloud message was received by the server. + /// + public DateTime EnqueuedTimeUtc + { + get => GetSystemProperty(MessageSystemPropertyNames.EnqueuedTime); + internal set => SystemProperties[MessageSystemPropertyNames.EnqueuedTime] = value; + } + + /// + /// Number of times the message has been previously delivered + /// + public uint DeliveryCount + { + get => GetSystemProperty(MessageSystemPropertyNames.DeliveryCount); + internal set => SystemProperties[MessageSystemPropertyNames.DeliveryCount] = (byte)value; + } + + /// + /// [Required in feedback messages] Used to specify the origin of messages generated by device hub. + /// Possible value: “{hub name}/” + /// + public string UserId + { + get => GetSystemProperty(MessageSystemPropertyNames.UserId); + set => SystemProperties[MessageSystemPropertyNames.UserId] = value; + } + + /// + /// Used to specify the schema of the message content. + /// + public string MessageSchema + { + get => GetSystemProperty(MessageSystemPropertyNames.MessageSchema); + set => SystemProperties[MessageSystemPropertyNames.MessageSchema] = value; + } + + /// + /// Custom date property set by the originator of the message. + /// + public DateTime CreationTimeUtc + { + get => GetSystemProperty(MessageSystemPropertyNames.CreationTimeUtc); + set => SystemProperties[MessageSystemPropertyNames.CreationTimeUtc] = value; + } + + /// + /// True if the message is set as a security message + /// + public bool IsSecurityMessage => CommonConstants.SecurityMessageInterfaceId.Equals(GetSystemProperty(MessageSystemPropertyNames.InterfaceId), StringComparison.Ordinal); + + /// + /// Used to specify the content type of the message. + /// + internal string PayloadContentType + { + get => GetSystemProperty(MessageSystemPropertyNames.ContentType); + set => SystemProperties[MessageSystemPropertyNames.ContentType] = value; + } + + /// + /// Specifies the input name on which the message was sent, if there was one. + /// + public string InputName + { + get => GetSystemProperty(MessageSystemPropertyNames.InputName); + internal set => SystemProperties[MessageSystemPropertyNames.InputName] = value; + } + + /// + /// Specifies the device Id from which this message was sent, if there is one. + /// + public string ConnectionDeviceId + { + get => GetSystemProperty(MessageSystemPropertyNames.ConnectionDeviceId); + internal set => SystemProperties[MessageSystemPropertyNames.ConnectionDeviceId] = value; + } + + /// + /// Specifies the module Id from which this message was sent, if there is one. + /// + public string ConnectionModuleId + { + get => GetSystemProperty(MessageSystemPropertyNames.ConnectionModuleId); + internal set => SystemProperties[MessageSystemPropertyNames.ConnectionModuleId] = value; + } + + /// + /// Used to specify the content encoding type of the message. + /// + internal string PayloadContentEncoding + { + get => GetSystemProperty(MessageSystemPropertyNames.ContentEncoding); + set => SystemProperties[MessageSystemPropertyNames.ContentEncoding] = value; + } + + /// + /// The DTDL component name from where the telemetry message has originated. + /// This is relevant only for plug and play certified devices. + /// + internal string ComponentName + { + get => GetSystemProperty(MessageSystemPropertyNames.ComponentName); + set => SystemProperties[MessageSystemPropertyNames.ComponentName] = value; + } + + /// + /// Gets the dictionary of user properties which are set when user send the data. + /// + public IDictionary Properties { get; internal set; } + + /// + /// The body stream of the current event data instance + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1721:Property names should not match get methods", + Justification = "Cannot remove public property on a public facing type")] + public Stream BodyStream { get { return _bodyStream; } protected set { _bodyStream = value; } } + + /// + /// For outgoing messages, contains the Mqtt topic that the message is being sent to + /// For incoming messages, contains the Mqtt topic that the message arrived on + /// + internal string MqttTopicName { get; set; } + + /// + /// Gets the dictionary of system properties which are managed internally. + /// + internal IDictionary SystemProperties { get; set; } + + /// + /// Gets or sets the deliveryTag which is used for server side check-pointing. + /// + internal ArraySegment DeliveryTag { get; set; } + + /// + /// Sets the message as an security message + /// + public void SetAsSecurityMessage() + { + SystemProperties[MessageSystemPropertyNames.InterfaceId] = CommonConstants.SecurityMessageInterfaceId; + } + + /// + /// Return the body stream of the current event data instance + /// + /// + /// throws if the method has been called. + /// throws if the event data has already been disposed. + /// This method can only be called once and afterwards method will throw . + public virtual Stream GetBodyStream() + { + ThrowIfDisposed(); + SetGetBodyCalled(); + return _bodyStream ?? Stream.Null; + } + + /// + /// This methods return the body stream as a byte array + /// + /// + /// throws if the event data has already been disposed. + public byte[] GetBytes() + { + ThrowIfDisposed(); + SetGetBodyCalled(); + if (_bodyStream == null) + { +#if NET451 + return new byte[] { }; +#else + return Array.Empty(); +#endif + } + + return ReadFullStream(_bodyStream); + } + + /// + /// Clones an existing instance and sets content body defined by on it. + /// + /// + /// The cloned message has the message as the original message. + /// User should treat the input byte array as immutable when sending the message. + /// + /// Message content to be set after clone. + /// A new instance of with body content defined by , + /// and user/system properties of the cloned instance. + /// + public Message CloneWithBody(in byte[] byteArray) + { + var result = new Message(byteArray); + + foreach (string key in Properties.Keys) + { + result.Properties.Add(key, Properties[key]); + } + + foreach (string key in SystemProperties.Keys) + { + result.SystemProperties.Add(key, SystemProperties[key]); + } + + return result; + } + + bool IReadOnlyIndicator.IsReadOnly => Interlocked.Read(ref _sizeInBytesCalled) == 1; + + /// + /// Dispose the current event data instance + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the Message object. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + DisposeBodyStream(); + } + } + + _disposed = true; + } + + /// + /// Disposes the body stream. + /// + protected void DisposeBodyStream() + { + if (_bodyStream != null && _streamDisposalResponsibility == StreamDisposalResponsibility.Sdk) + { + _bodyStream.Dispose(); + _bodyStream = null; + } + } + + internal void ThrowIfDisposed() + { + if (_disposed) + { + throw Fx.Exception.ObjectDisposed(ApiResources.MessageDisposed); + } + } + + internal bool HasBodyStream() + { + return _bodyStream != null; + } + + internal void ResetBody() + { + if (_originalStreamPosition == StreamCannotSeek) + { + throw new IOException("Stream cannot seek."); + } + + _bodyStream.Seek(_originalStreamPosition, SeekOrigin.Begin); + Interlocked.Exchange(ref _getBodyCalled, 0); + } + + internal bool IsBodyCalled => Volatile.Read(ref _getBodyCalled) == 1; + + private void InitializeWithStream(Stream stream, StreamDisposalResponsibility streamDisposalResponsibility) + { + // This method should only be used in constructor because + // this has no locking on the bodyStream. + _bodyStream = stream; + _streamDisposalResponsibility = streamDisposalResponsibility; + + if (_bodyStream.CanSeek) + { + _originalStreamPosition = _bodyStream.Position; + } + } + + private void SetGetBodyCalled() + { + if (1 == Interlocked.Exchange(ref _getBodyCalled, 1)) + { + throw Fx.Exception.AsError(new InvalidOperationException(ApiResources.MessageBodyConsumed)); + } + } + + private static byte[] ReadFullStream(Stream inputStream) + { + using var ms = new MemoryStream(); + inputStream.CopyTo(ms); + return ms.ToArray(); + } + + /// + /// Retrieves the value for the specified key from the SystemProperties dictionary. + /// + /// The type to cast the retrieved value to. + /// The key whose value is to be retrieved. + /// The value for the specified key from the SystemProperties dictionary + protected T GetSystemProperty(string key) + { + return SystemProperties.ContainsKey(key) + ? (T)SystemProperties[key] + : default; + } + } +} diff --git a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs new file mode 100644 index 0000000000..4c15885a2a --- /dev/null +++ b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs @@ -0,0 +1,30 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// Contains methods that a convention based module can use to send telemetry to the service, + /// respond to commands and perform operations on its properties. + /// + /// + public partial class ModuleClient : IDisposable + { + /// + /// Send telemetry using the specified message. + /// + /// + /// Use the constructor to pass in the optional component name + /// that the telemetry message is from. + /// + /// The telemetry message. + /// A cancellation token to cancel the operation. + /// + public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default) + => InternalClient.SendTelemetryAsync(telemetryMessage, cancellationToken); + } +} diff --git a/iothub/device/src/ModuleClient.cs b/iothub/device/src/ModuleClient.cs index 8970dd1f0d..04a63de0ec 100644 --- a/iothub/device/src/ModuleClient.cs +++ b/iothub/device/src/ModuleClient.cs @@ -21,7 +21,7 @@ namespace Microsoft.Azure.Devices.Client /// /// Contains methods that a module can use to send messages to and receive from the service and interact with module twins. /// - public class ModuleClient : IDisposable + public partial class ModuleClient : IDisposable { private const string ModuleMethodUriFormat = "/twins/{0}/modules/{1}/methods?" + ClientApiVersionHelper.ApiVersionQueryStringLatest; private const string DeviceMethodUriFormat = "/twins/{0}/methods?" + ClientApiVersionHelper.ApiVersionQueryStringLatest; diff --git a/iothub/device/src/TelemetryCollection.cs b/iothub/device/src/TelemetryCollection.cs new file mode 100644 index 0000000000..677a6d16e0 --- /dev/null +++ b/iothub/device/src/TelemetryCollection.cs @@ -0,0 +1,37 @@ +// 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 Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// The telmetry collection used to populate a . + /// + public class TelemetryCollection : PayloadCollection + { + /// + /// Adds the telemetry to the telemetry collection. + /// + /// + /// + /// + /// An element with the same key already exists in the collection. + public override void Add(string telemetryName, object telemetryValue) + { + base.Add(telemetryName, telemetryValue); + } + + /// + /// Adds or updates the telemetry collection. + /// + /// The name of the telemetry. + /// The value of the telemetry. + /// is null + public override void AddOrUpdate(string telemetryName, object telemetryValue) + { + base.AddOrUpdate(telemetryName, telemetryValue); + } + } +} diff --git a/iothub/device/src/TelemetryMessage.cs b/iothub/device/src/TelemetryMessage.cs new file mode 100644 index 0000000000..78a5c8c552 --- /dev/null +++ b/iothub/device/src/TelemetryMessage.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// A class used to send telemetry to IoT Hub. + /// + /// + /// This class is derived from and is designed to accept a + /// to utilize the to adhere to a well defined convention. + /// + public sealed class TelemetryMessage : MessageBase + { + /// + /// Gets or sets the for this + /// + /// A telemetry collection that will be set as the message payload. + public TelemetryCollection Telemetry { get; set; } = new TelemetryCollection(); + + /// + /// A convenience constructor that allows you to set the of this + /// + /// The name of the component. + public TelemetryMessage(string componentName = default) + : base() + { + if (!string.IsNullOrEmpty(componentName)) + { + ComponentName = componentName; + } + } + + /// + public override Stream GetBodyStream() + { + DisposeBodyStream(); + BodyStream = new MemoryStream(Telemetry.GetPayloadObjectBytes()); + return base.GetBodyStream(); + } + } +} diff --git a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs index c1fc594c66..e4ec03001c 100644 --- a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs +++ b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs @@ -152,7 +152,7 @@ public override async Task CloseAsync(CancellationToken cancellationToken) #region Telemetry - public override async Task SendEventAsync(Message message, CancellationToken cancellationToken) + public override async Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { Logging.Enter(this, message, cancellationToken, nameof(SendEventAsync)); @@ -168,7 +168,7 @@ public override async Task SendEventAsync(Message message, CancellationToken can } } - public override async Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) + public override async Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) { Logging.Enter(this, messages, cancellationToken, nameof(SendEventAsync)); diff --git a/iothub/device/src/Transport/Amqp/AmqpUnit.cs b/iothub/device/src/Transport/Amqp/AmqpUnit.cs index 359424eb1a..f2f62d9fbb 100644 --- a/iothub/device/src/Transport/Amqp/AmqpUnit.cs +++ b/iothub/device/src/Transport/Amqp/AmqpUnit.cs @@ -246,7 +246,7 @@ private async Task EnsureMessageReceivingLinkIsOpenAsync(TimeSpan timeout, bool } } - public async Task SendMessagesAsync(IEnumerable messages, TimeSpan timeout) + public async Task SendMessagesAsync(IEnumerable messages, TimeSpan timeout) { Logging.Enter(this, messages, timeout, nameof(SendMessagesAsync)); @@ -262,7 +262,7 @@ public async Task SendMessagesAsync(IEnumerable message } } - public async Task SendMessageAsync(Message message, TimeSpan timeout) + public async Task SendMessageAsync(MessageBase message, TimeSpan timeout) { Logging.Enter(this, message, timeout, nameof(SendMessageAsync)); @@ -463,7 +463,7 @@ public async Task EnableEventReceiveAsync(TimeSpan timeout) } } - public async Task SendEventsAsync(IEnumerable messages, TimeSpan timeout) + public async Task SendEventsAsync(IEnumerable messages, TimeSpan timeout) { Logging.Enter(this, messages, timeout, nameof(SendEventsAsync)); @@ -477,7 +477,7 @@ public async Task SendEventsAsync(IEnumerable messages, } } - public async Task SendEventAsync(Message message, TimeSpan timeout) + public async Task SendEventAsync(MessageBase message, TimeSpan timeout) { Logging.Enter(this, message, timeout, nameof(SendEventAsync)); diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotMessageConverter.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotMessageConverter.cs index e2a57e229f..5b3c72f3b9 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotMessageConverter.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotMessageConverter.cs @@ -46,11 +46,11 @@ public static Message AmqpMessageToMessage(AmqpMessage amqpMessage) return message; } - public static AmqpMessage MessageToAmqpMessage(Message message) + public static AmqpMessage MessageToAmqpMessage(MessageBase message) { if (message == null) { - throw Fx.Exception.ArgumentNull(nameof(Message)); + throw Fx.Exception.ArgumentNull(nameof(MessageBase)); } message.ThrowIfDisposed(); @@ -181,7 +181,7 @@ public static void UpdateMessageHeaderAndProperties(AmqpMessage amqpMessage, Mes /// /// Copies the Message instance's properties to the AmqpMessage instance. /// - public static void UpdateAmqpMessageHeadersAndProperties(AmqpMessage amqpMessage, Message data, bool copyUserProperties = true) + public static void UpdateAmqpMessageHeadersAndProperties(AmqpMessage amqpMessage, MessageBase data, bool copyUserProperties = true) { amqpMessage.Properties.MessageId = data.MessageId; diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs index da3aadfe4f..ae932f68d5 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs @@ -73,7 +73,7 @@ internal bool IsClosing() #region Telemetry handling - internal async Task SendMessageAsync(Message message, TimeSpan timeout) + internal async Task SendMessageAsync(MessageBase message, TimeSpan timeout) { if (Logging.IsEnabled) { @@ -93,7 +93,7 @@ internal async Task SendMessageAsync(Message message, TimeSpan t return new AmqpIotOutcome(outcome); } - internal async Task SendMessagesAsync(IEnumerable messages, TimeSpan timeout) + internal async Task SendMessagesAsync(IEnumerable messages, TimeSpan timeout) { if (Logging.IsEnabled) { diff --git a/iothub/device/src/Transport/DefaultDelegatingHandler.cs b/iothub/device/src/Transport/DefaultDelegatingHandler.cs index 6db75b1e18..d239eaf125 100644 --- a/iothub/device/src/Transport/DefaultDelegatingHandler.cs +++ b/iothub/device/src/Transport/DefaultDelegatingHandler.cs @@ -136,13 +136,13 @@ public virtual Task RejectAsync(string lockToken, CancellationToken cancellation return InnerHandler?.RejectAsync(lockToken, cancellationToken) ?? TaskHelpers.CompletedTask; } - public virtual Task SendEventAsync(Message message, CancellationToken cancellationToken) + public virtual Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { ThrowIfDisposed(); return InnerHandler?.SendEventAsync(message, cancellationToken) ?? TaskHelpers.CompletedTask; } - public virtual Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) + public virtual Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) { ThrowIfDisposed(); return InnerHandler?.SendEventAsync(messages, cancellationToken) ?? TaskHelpers.CompletedTask; diff --git a/iothub/device/src/Transport/ErrorDelegatingHandler.cs b/iothub/device/src/Transport/ErrorDelegatingHandler.cs index 4d4b169a8e..ee6c9e2d5b 100644 --- a/iothub/device/src/Transport/ErrorDelegatingHandler.cs +++ b/iothub/device/src/Transport/ErrorDelegatingHandler.cs @@ -130,12 +130,12 @@ public override Task RejectAsync(string lockToken, CancellationToken cancellatio return ExecuteWithErrorHandlingAsync(() => base.RejectAsync(lockToken, cancellationToken)); } - public override Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) + public override Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) { return ExecuteWithErrorHandlingAsync(() => base.SendEventAsync(messages, cancellationToken)); } - public override Task SendEventAsync(Message message, CancellationToken cancellationToken) + public override Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { return ExecuteWithErrorHandlingAsync(() => base.SendEventAsync(message, cancellationToken)); } diff --git a/iothub/device/src/Transport/HttpTransportHandler.cs b/iothub/device/src/Transport/HttpTransportHandler.cs index b57150cdc9..7917452398 100644 --- a/iothub/device/src/Transport/HttpTransportHandler.cs +++ b/iothub/device/src/Transport/HttpTransportHandler.cs @@ -93,7 +93,7 @@ public override Task CloseAsync(CancellationToken cancellationToken) return TaskHelpers.CompletedTask; } - public override Task SendEventAsync(Message message, CancellationToken cancellationToken) + public override Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { Debug.Assert(message != null); cancellationToken.ThrowIfCancellationRequested(); @@ -120,7 +120,7 @@ public override Task SendEventAsync(Message message, CancellationToken cancellat cancellationToken); } - public override Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) + public override Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) { if (messages == null) { @@ -494,7 +494,7 @@ private static Uri GetRequestUri(string deviceId, string path, IDictionary messages) + private static string ToJson(IEnumerable messages) { using var sw = new StringWriter(); using var writer = new JsonTextWriter(sw); @@ -502,7 +502,7 @@ private static string ToJson(IEnumerable messages) // [ writer.WriteStartArray(); - foreach (Message message in messages) + foreach (MessageBase message in messages) { // { writer.WriteStartObject(); diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs index caaf8959c4..43ae6551e0 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs @@ -230,7 +230,7 @@ public override async Task OpenAsync(CancellationToken cancellationToken) } } - public override async Task SendEventAsync(Message message, CancellationToken cancellationToken) + public override async Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { try { @@ -251,7 +251,7 @@ public override async Task SendEventAsync(Message message, CancellationToken can } } - public override async Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) + public override async Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) { foreach (Message message in messages) { diff --git a/iothub/device/src/Transport/RetryDelegatingHandler.cs b/iothub/device/src/Transport/RetryDelegatingHandler.cs index 716770fe7e..c3d0c2285b 100644 --- a/iothub/device/src/Transport/RetryDelegatingHandler.cs +++ b/iothub/device/src/Transport/RetryDelegatingHandler.cs @@ -67,7 +67,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) Logging.Associate(this, _internalRetryPolicy, nameof(SetRetryPolicy)); } - public override async Task SendEventAsync(Message message, CancellationToken cancellationToken) + public override async Task SendEventAsync(MessageBase message, CancellationToken cancellationToken) { try { @@ -95,7 +95,7 @@ await _internalRetryPolicy } } - public override async Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) + public override async Task SendEventAsync(IEnumerable messages, CancellationToken cancellationToken) { try { diff --git a/iothub/device/tests/Mqtt/MqttIotHubAdapterTest.cs b/iothub/device/tests/Mqtt/MqttIotHubAdapterTest.cs index 446420e58b..eb64d3c90c 100644 --- a/iothub/device/tests/Mqtt/MqttIotHubAdapterTest.cs +++ b/iothub/device/tests/Mqtt/MqttIotHubAdapterTest.cs @@ -36,8 +36,9 @@ public void TestPopulateMessagePropertiesFromPacket_NormalMessage() Assert.AreEqual("Value3", message.Properties["Prop3"]); Assert.AreEqual(3, message.SystemProperties.Count); - Assert.AreEqual("Corrid1", message.SystemProperties["correlation-id"]); - Assert.AreEqual("MessageId1", message.SystemProperties["message-id"]); + Assert.AreEqual("Corrid1", message.SystemProperties[MessageSystemPropertyNames.CorrelationId]); + Assert.AreEqual("MessageId1", message.SystemProperties[MessageSystemPropertyNames.MessageId]); + Assert.AreEqual(null, message.SystemProperties[MessageSystemPropertyNames.LockToken]); } [TestMethod] @@ -57,8 +58,9 @@ public void TestPopulateMessagePropertiesFromPacket_ModuleEndpointMessage() Assert.AreEqual("Value3", message.Properties["Prop3"]); Assert.AreEqual(3, message.SystemProperties.Count); - Assert.AreEqual("Corrid1", message.SystemProperties["correlation-id"]); - Assert.AreEqual("MessageId1", message.SystemProperties["message-id"]); + Assert.AreEqual("Corrid1", message.SystemProperties[MessageSystemPropertyNames.CorrelationId]); + Assert.AreEqual("MessageId1", message.SystemProperties[MessageSystemPropertyNames.MessageId]); + Assert.AreEqual(null, message.SystemProperties[MessageSystemPropertyNames.LockToken]); } [TestMethod] From 5285c79da6fd632331dff83ddb1d0e9466665306 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 20 May 2021 16:16:35 -0700 Subject: [PATCH 03/77] feat(iot-device): Add support for convention-based command operations --- iothub/device/src/CommandRequest.cs | 67 +++++++++++++++++++ iothub/device/src/CommandResponse.cs | 50 ++++++++++++++ iothub/device/src/Common/Utils.cs | 9 ++- .../DeviceClient.ConventionBasedOperations.cs | 11 +++ ...nternalClient.ConventionBasedOperations.cs | 30 +++++++++ .../ModuleClient.ConventionBasedOperations.cs | 11 +++ shared/src/ConventionBasedConstants.cs | 5 ++ 7 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 iothub/device/src/CommandRequest.cs create mode 100644 iothub/device/src/CommandResponse.cs diff --git a/iothub/device/src/CommandRequest.cs b/iothub/device/src/CommandRequest.cs new file mode 100644 index 0000000000..9b434f34a6 --- /dev/null +++ b/iothub/device/src/CommandRequest.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// The data structure that represents a convention based command request. + /// + public sealed class CommandRequest + { + private readonly byte[] _data; + private readonly PayloadConvention _payloadConvention; + + internal CommandRequest(PayloadConvention payloadConvention, string commandName, string componentName = default, byte[] data = default) + { + CommandName = commandName; + ComponentName = componentName; + _data = data; + _payloadConvention = payloadConvention; + } + + /// + /// The name of the component that is command is invoked on. + /// + public string ComponentName { get; private set; } + + /// + /// The command name. + /// + public string CommandName { get; private set; } + + /// + /// The command request data. + /// + /// The type to cast the command request data to. + /// The command request data. + public T GetData() + { + string dataAsJson = DataAsJson; + + return dataAsJson == null + ? default + : _payloadConvention.PayloadSerializer.DeserializeToType(dataAsJson); + } + + /// + /// The command request data bytes. + /// + /// + /// The command request data bytes. + /// + public byte[] GetDataAsBytes() + { + // Need to return a clone of the array so that consumers + // of this library cannot change its contents. + return (byte[])_data.Clone(); + } + + /// + /// The command data in Json format. + /// + public string DataAsJson => (_data == null || _data.Length == 0) ? null : Encoding.UTF8.GetString(_data); + } +} diff --git a/iothub/device/src/CommandResponse.cs b/iothub/device/src/CommandResponse.cs new file mode 100644 index 0000000000..465cecc78e --- /dev/null +++ b/iothub/device/src/CommandResponse.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// The command response that the client responds with. + /// + public sealed class CommandResponse + { + private readonly object _result; + + internal PayloadConvention PayloadConvention { get; set; } + + /// + /// Creates a new instance of the class with the associated command response data and a status code. + /// + /// The command response data. + /// A status code indicating success or failure. + public CommandResponse(object result, int status) + { + _result = result; + Status = status; + } + + /// + /// Creates a new instance of the class with the associated status code. + /// + /// A status code indicating success or failure. + public CommandResponse(int status) + { + Status = status; + } + + /// + /// The command response status code indicating success or failure. + /// + public int Status { get; private set; } + + /// + /// The serialized command response data. + /// + public string ResultAsJson => _result == null ? null : PayloadConvention.PayloadSerializer.SerializeToString(_result); + + internal byte[] ResultAsBytes => _result == null ? null : Encoding.UTF8.GetBytes(ResultAsJson); + } +} diff --git a/iothub/device/src/Common/Utils.cs b/iothub/device/src/Common/Utils.cs index a858b3ed2a..723bdfc762 100644 --- a/iothub/device/src/Common/Utils.cs +++ b/iothub/device/src/Common/Utils.cs @@ -99,11 +99,14 @@ public static void ValidateDataIsEmptyOrJson(byte[] data) if (data != null && data.Length != 0) { - var stream = new MemoryStream(data); - var streamReader = new StreamReader(stream, Encoding.UTF8, false, Math.Min(1024, data.Length)); + using var stream = new MemoryStream(data); + using var streamReader = new StreamReader(stream, Encoding.UTF8, false, Math.Min(1024, data.Length)); using var reader = new JsonTextReader(streamReader); - while (reader.Read()) { } + + while (reader.Read()) + { + } } } diff --git a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs index 70db59d4fd..7afb17c35d 100644 --- a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs +++ b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs @@ -26,5 +26,16 @@ public partial class DeviceClient : IDisposable /// public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default) => InternalClient.SendTelemetryAsync(telemetryMessage, cancellationToken); + + /// + /// Set the global command callback handler. + /// + /// A method implementation that will handle the incoming command. + /// Generic parameter to be interpreted by the client code. + /// A cancellation token to cancel the operation. + public Task SubscribeToCommandsAsync( + Func> callback, object userContext, + CancellationToken cancellationToken = default) + => InternalClient.SubscribeToCommandsAsync(callback, userContext, cancellationToken); } } diff --git a/iothub/device/src/InternalClient.ConventionBasedOperations.cs b/iothub/device/src/InternalClient.ConventionBasedOperations.cs index 4cd5607bd1..e92f0d7beb 100644 --- a/iothub/device/src/InternalClient.ConventionBasedOperations.cs +++ b/iothub/device/src/InternalClient.ConventionBasedOperations.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Shared; @@ -32,5 +33,34 @@ internal Task SendTelemetryAsync(TelemetryMessage telemetryMessage, Cancellation return SendEventAsync(telemetryMessage, cancellationToken); } + + internal Task SubscribeToCommandsAsync(Func> callback, object userContext, CancellationToken cancellationToken) + { + // Subscribe to methods default handler internally and use the callback received internally to invoke the user supplied command callback. + var methodDefaultCallback = new MethodCallback(async (methodRequest, userContext) => + { + CommandRequest commandRequest; + if (methodRequest.Name != null + && methodRequest.Name.Contains(ConventionBasedConstants.ComponentLevelCommandSeparator)) + { + string[] split = methodRequest.Name.Split(ConventionBasedConstants.ComponentLevelCommandSeparator); + string componentName = split[0]; + string commandName = split[1]; + commandRequest = new CommandRequest(PayloadConvention, commandName, componentName, methodRequest.Data); + } + else + { + commandRequest = new CommandRequest(payloadConvention: PayloadConvention, commandName: methodRequest.Name, data: methodRequest.Data); + } + + CommandResponse commandResponse = await callback.Invoke(commandRequest, userContext).ConfigureAwait(false); + commandResponse.PayloadConvention = PayloadConvention; + return commandResponse.ResultAsBytes != null + ? new MethodResponse(commandResponse.ResultAsBytes, commandResponse.Status) + : new MethodResponse(commandResponse.Status); + }); + + return SetMethodDefaultHandlerAsync(methodDefaultCallback, userContext, cancellationToken); + } } } diff --git a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs index 4c15885a2a..d8e19cf128 100644 --- a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs +++ b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs @@ -26,5 +26,16 @@ public partial class ModuleClient : IDisposable /// public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default) => InternalClient.SendTelemetryAsync(telemetryMessage, cancellationToken); + + /// + /// Set the global command callback handler. + /// + /// A method implementation that will handle the incoming command. + /// Generic parameter to be interpreted by the client code. + /// A cancellation token to cancel the operation. + public Task SubscribeToCommandsAsync( + Func> callback, object userContext, + CancellationToken cancellationToken = default) + => InternalClient.SubscribeToCommandsAsync(callback, userContext, cancellationToken); } } diff --git a/shared/src/ConventionBasedConstants.cs b/shared/src/ConventionBasedConstants.cs index 1d4649fc5e..2313acf92e 100644 --- a/shared/src/ConventionBasedConstants.cs +++ b/shared/src/ConventionBasedConstants.cs @@ -8,6 +8,11 @@ namespace Microsoft.Azure.Devices.Shared /// public static class ConventionBasedConstants { + /// + /// Separator for a component-level command name. + /// + public const char ComponentLevelCommandSeparator = '*'; + /// /// Marker key to indicate a component-level property. /// From 88c2155d641da327981ffe1b715eb70739cec2c6 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 21 May 2021 16:34:26 -0700 Subject: [PATCH 04/77] * feat(iot-device): Add support for convention-based properties operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(iot-device): Add support for convention-based properties operations Co-authored-by: James Davis ⛺️🏔 --- iothub/device/src/ClientProperties.cs | 45 +++ .../src/ClientPropertiesUpdateResponse.cs | 25 ++ iothub/device/src/ClientPropertyCollection.cs | 379 ++++++++++++++++++ iothub/device/src/ClientTwinProperties.cs | 32 ++ .../Common/UrlEncodedDictionarySerializer.cs | 11 +- .../DeviceClient.ConventionBasedOperations.cs | 28 +- iothub/device/src/IDelegatingHandler.cs | 6 + ...nternalClient.ConventionBasedOperations.cs | 48 +++ iothub/device/src/MessageBase.cs | 38 +- .../ModuleClient.ConventionBasedOperations.cs | 28 +- .../Transport/Amqp/AmqpTransportHandler.cs | 20 + .../src/Transport/DefaultDelegatingHandler.cs | 12 + .../src/Transport/ErrorDelegatingHandler.cs | 10 + .../src/Transport/HttpTransportHandler.cs | 10 + .../src/Transport/Mqtt/MqttIotHubAdapter.cs | 8 +- .../Transport/Mqtt/MqttTransportHandler.cs | 63 ++- .../src/Transport/RetryDelegatingHandler.cs | 44 ++ .../tests/Mqtt/MqttTransportHandlerTests.cs | 37 +- shared/src/NewtonsoftJsonPayloadSerializer.cs | 2 +- shared/src/StatusCodes.cs | 33 ++ 20 files changed, 841 insertions(+), 38 deletions(-) create mode 100644 iothub/device/src/ClientProperties.cs create mode 100644 iothub/device/src/ClientPropertiesUpdateResponse.cs create mode 100644 iothub/device/src/ClientPropertyCollection.cs create mode 100644 iothub/device/src/ClientTwinProperties.cs create mode 100644 shared/src/StatusCodes.cs diff --git a/iothub/device/src/ClientProperties.cs b/iothub/device/src/ClientProperties.cs new file mode 100644 index 0000000000..08520d5792 --- /dev/null +++ b/iothub/device/src/ClientProperties.cs @@ -0,0 +1,45 @@ +// 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.Client +{ + /// + /// A container for properties retrieved from the service. + /// + /// + /// The class is not meant to be constructed by customer code. + /// It is intended to be returned fully populated from the client method . + /// + public class ClientProperties : ClientPropertyCollection + { + /// + /// Initializes a new instance of . + /// This is provided for unit testing purposes only. + /// + /// + public ClientProperties() + { + Writable = new ClientPropertyCollection(); + } + + /// + /// Initializes a new instance of with the specified collections. + /// + /// A collection of writable properties returned from IoT Hub. + /// A collection of read-only properties returned from IoT Hub. + internal ClientProperties(ClientPropertyCollection requestedPropertyCollection, ClientPropertyCollection readOnlyPropertyCollection) + { + SetCollection(readOnlyPropertyCollection); + Version = readOnlyPropertyCollection.Version; + Writable = requestedPropertyCollection; + } + + /// + /// The collection of writable properties. + /// + /// + /// See the Writable properties documentation for more information. + /// + public ClientPropertyCollection Writable { get; private set; } + } +} diff --git a/iothub/device/src/ClientPropertiesUpdateResponse.cs b/iothub/device/src/ClientPropertiesUpdateResponse.cs new file mode 100644 index 0000000000..a23deb32f3 --- /dev/null +++ b/iothub/device/src/ClientPropertiesUpdateResponse.cs @@ -0,0 +1,25 @@ +// 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.Client +{ + /// + /// The response of an operation. + /// + public class ClientPropertiesUpdateResponse + { + /// + /// The request Id that is associated with the operation. + /// + /// + /// This request Id is relevant only for operations over MQTT, and can be used for tracking the operation on service side logs. + /// Note that you would need to contact the support team to track operations on the service side. + /// + public string RequestId { get; internal set; } + + /// + /// The updated version after the property patch has been applied. + /// + public long Version { get; internal set; } + } +} diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs new file mode 100644 index 0000000000..df9e17dbad --- /dev/null +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -0,0 +1,379 @@ +// 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 Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// A collection of properties for the client. + /// + public class ClientPropertyCollection : PayloadCollection + { + private const string VersionName = "$version"; + + /// + /// + /// Adds the value to the collection. + /// + /// + /// If the collection has a key that matches the property name this method will throw an . + /// + /// When using this as part of the writable property flow to respond to a writable property update you should pass in the value + /// as an instance of + /// to ensure the correct formatting is applied when the object is serialized. + /// + /// + /// is null + /// The name of the property to add. + /// The value of the property to add. + public override void Add(string propertyName, object propertyValue) + => Add(null, propertyName, propertyValue); + + /// + /// + /// + /// + /// Adds the value to the collection. + /// + /// is null + /// The component with the property to add. + /// The name of the property to add. + /// The value of the property to add. + public void Add(string componentName, string propertyName, object propertyValue) + => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, false); + + /// + /// + /// + /// + /// Adds the values to the collection. + /// + /// A collection of properties to add or update. + public void Add(IDictionary properties) + => AddInternal(properties, null, false); + + /// + /// + /// + /// + /// Adds the values to the collection. + /// + /// A collection of properties to add or update. + /// The component with the properties to add or update. + public void Add(string componentName, IDictionary properties) + => AddInternal(properties, componentName, false); + + /// + /// + /// + /// Adds a writable property to the collection. + /// + /// + /// This method will use the method to create an instance of that will be properly serialized. + /// + /// is null + /// The name of the property to add or update. + /// The value of the property to add or update. + /// + /// + /// + public void Add(string propertyName, object propertyValue, int statusCode, long version, string description = default) + => Add(null, propertyName, propertyValue, statusCode, version, description); + + /// + /// + /// + /// Adds a writable property to the collection. + /// + /// + /// This method will use the method to create an instance of that will be properly serialized. + /// + /// is null + /// The name of the property to add or update. + /// The value of the property to add or update. + /// + /// + /// + /// + public void Add(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = default) + { + if (Convention?.PayloadSerializer == null) + { + Add(componentName, propertyName, new { value = propertyValue, ac = statusCode, av = version, ad = description }); + } + else + { + Add(componentName, propertyName, Convention.PayloadSerializer.CreateWritablePropertyResponse(propertyValue, statusCode, version, description)); + } + } + + /// + /// + /// + /// + /// is null + /// The name of the property to add or update. + /// The value of the property to add or update. + public override void AddOrUpdate(string propertyName, object propertyValue) + => AddOrUpdate(null, propertyName, propertyValue); + + /// + /// + /// + /// + /// is null + /// The component with the property to add or update. + /// The name of the property to add or update. + /// The value of the property to add or update. + public void AddOrUpdate(string componentName, string propertyName, object propertyValue) + => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); + + /// + /// + /// + /// + /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. + /// + /// When using this as part of the writable property flow to respond to a writable property update + /// you should pass in the value as an instance of + /// to ensure the correct formatting is applied when the object is serialized. + /// + /// + /// A collection of properties to add or update. + public void AddOrUpdate(IDictionary properties) + => AddInternal(properties, null, true); + + /// + /// + /// + /// + /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. + /// + /// When using this as part of the writable property flow to respond to a writable property update + /// you should pass in the value as an instance of + /// to ensure the correct formatting is applied when the object is serialized. + /// + /// + /// The component with the properties to add or update. + /// A collection of properties to add or update. + public void AddOrUpdate(string componentName, IDictionary properties) + => AddInternal(properties, componentName, true); + + /// + /// + /// + /// Adds or updates a type of to the collection. + /// + /// is null + /// The name of the writable property to add or update. + /// The value of the writable property to add or update. + /// + /// + /// + public void AddOrUpdate(string propertyName, object propertyValue, int statusCode, long version, string description = default) + => AddOrUpdate(null, propertyName, propertyValue, statusCode, version, description); + + /// + /// + /// + /// + /// Adds or updates a type of to the collection. + /// + /// is null + /// The name of the writable property to add or update. + /// The value of the writable property to add or update. + /// + /// + /// + /// + public void AddOrUpdate(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = default) + { + if (Convention?.PayloadSerializer == null) + { + AddOrUpdate(componentName, propertyName, new { value = propertyValue, ac = statusCode, av = version, ad = description }); + } + else + { + AddOrUpdate(componentName, propertyName, Convention.PayloadSerializer.CreateWritablePropertyResponse(propertyValue, statusCode, version, description)); + } + } + + /// + /// Adds or updates the value for the collection. + /// + /// + /// + /// + /// A collection of properties to add or update. + /// The component with the properties to add or update. + /// Forces the collection to use the Add or Update behavior. + /// Setting to true will simply overwrite the value. Setting to false will use + private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) + { + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + + // If the componentName is null then simply add the key-value pair to Collection dictionary. + // this will either insert a property or overwrite it if it already exists. + if (componentName == null) + { + foreach (KeyValuePair entry in properties) + { + if (forceUpdate) + { + Collection[entry.Key] = entry.Value; + } + else + { + Collection.Add(entry.Key, entry.Value); + } + } + } + else + { + // If the component name already exists within the dictionary, then the value is a dictionary containing the component level property key and values. + // Append this property dictionary to the existing property value dictionary (overwrite entries if they already exist). + // Otherwise, add this as a new entry. + var componentProperties = new Dictionary(); + if (Collection.ContainsKey(componentName)) + { + componentProperties = (Dictionary)Collection[componentName]; + } + foreach (KeyValuePair entry in properties) + { + if (forceUpdate) + { + componentProperties[entry.Key] = entry.Value; + } + else + { + componentProperties.Add(entry.Key, entry.Value); + } + } + + // For a component level property, the property patch needs to contain the {"__t": "c"} component identifier. + if (!componentProperties.ContainsKey(ConventionBasedConstants.ComponentIdentifierKey)) + { + componentProperties[ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue; + } + + if (forceUpdate) + { + Collection[componentName] = componentProperties; + } + else + { + Collection.Add(componentName, componentProperties); + } + } + } + + /// + /// Determines whether the specified property is present. + /// + /// The property to locate. + /// The component which holds the required property. + /// true if the specified property is present; otherwise, false. + public bool Contains(string componentName, string propertyName) + { + if (!string.IsNullOrEmpty(componentName) && Collection.TryGetValue(componentName, out var component)) + { + return Convention.PayloadSerializer.TryGetNestedObjectValue(component, propertyName, out _); + } + return Collection.TryGetValue(propertyName, out _); + } + + /// + /// Gets the version of the property collection. + /// + /// A that is used to identify the version of the property collection. + public long Version { get; protected set; } + + /// + /// Gets the value of a component-level property. + /// + /// + /// To get the value of a root-level property use . + /// + /// The type to cast the object to. + /// The component which holds the required property. + /// The property to get. + /// The value of the component-level property. + /// true if the property collection contains a component level property with the specified key; otherwise, false. + public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue) + { + if (Contains(componentName, propertyName)) + { + object componentProperties = Collection[componentName]; + Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); + return true; + } + + propertyValue = default; + return false; + } + + /// + /// Converts a collection to a properties collection. + /// + /// This internal class is aware of the implementation of the TwinCollection ad will + /// The TwinCollection object to convert. + /// A convention handler that defines the content encoding and serializer to use for the payload. + /// A new instance of the class from an existing using an optional . + internal static ClientPropertyCollection FromTwinCollection(TwinCollection twinCollection, PayloadConvention payloadConvention) + { + if (twinCollection == null) + { + throw new ArgumentNullException(nameof(twinCollection)); + } + + var propertyCollectionToReturn = new ClientPropertyCollection + { + Convention = payloadConvention, + }; + + foreach (KeyValuePair property in twinCollection) + { + propertyCollectionToReturn.Add(property.Key, payloadConvention.PayloadSerializer.DeserializeToType(Newtonsoft.Json.JsonConvert.SerializeObject(property.Value))); + } + // The version information is not accessible via the enumerator, so assign it separately. + propertyCollectionToReturn.Version = twinCollection.Version; + + return propertyCollectionToReturn; + } + + internal static ClientPropertyCollection FromClientTwinDictionary(IDictionary clientTwinPropertyDictionary, PayloadConvention payloadConvention) + { + if (clientTwinPropertyDictionary == null) + { + throw new ArgumentNullException(nameof(clientTwinPropertyDictionary)); + } + + var propertyCollectionToReturn = new ClientPropertyCollection + { + Convention = payloadConvention, + }; + + foreach (KeyValuePair property in clientTwinPropertyDictionary) + { + // The version information should not be a part of the enumerable ProperyCollection, but rather should be + // accessible through its dedicated accessor. + if (property.Key == VersionName) + { + propertyCollectionToReturn.Version = (long)property.Value; + } + else + { + propertyCollectionToReturn.Add(property.Key, payloadConvention.PayloadSerializer.DeserializeToType(Newtonsoft.Json.JsonConvert.SerializeObject(property.Value))); + } + } + + return propertyCollectionToReturn; + } + } +} diff --git a/iothub/device/src/ClientTwinProperties.cs b/iothub/device/src/ClientTwinProperties.cs new file mode 100644 index 0000000000..63b13195f5 --- /dev/null +++ b/iothub/device/src/ClientTwinProperties.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Microsoft.Azure.Devices.Shared; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.Client +{ + internal class ClientTwinProperties + { + internal ClientTwinProperties() + { + Desired = new Dictionary(); + Reported = new Dictionary(); + } + + [JsonProperty(PropertyName = "desired", DefaultValueHandling = DefaultValueHandling.Ignore)] + internal IDictionary Desired { get; set; } + + [JsonProperty(PropertyName = "reported", DefaultValueHandling = DefaultValueHandling.Ignore)] + internal IDictionary Reported { get; set; } + + internal ClientProperties ToClientProperties(PayloadConvention payloadConvention) + { + ClientPropertyCollection writablePropertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Desired, payloadConvention); + ClientPropertyCollection propertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Reported, payloadConvention); + + return new ClientProperties(writablePropertyCollection, propertyCollection); + } + } +} diff --git a/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs b/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs index 9bddbdf035..b3e87099a3 100644 --- a/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs +++ b/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs @@ -24,6 +24,11 @@ public class UrlEncodedDictionarySerializer /// public const char PropertySeparator = '&'; + /// + /// The character that marks the start of a query string. + /// + public const string QueryStringIdentifier = "?"; + /// /// The length of property separator string. /// @@ -337,7 +342,11 @@ public IEnumerable GetTokens() private Token CreateToken(TokenType tokenType, int readCount) { - string tokenValue = readCount == 0 ? null : value.Substring(position - readCount, readCount); + // '?' is not a valid character for message property names or values, but instead signifies the start of a query string + // in the case of an MQTT topic. For this reason, we'll replace the '?' from the property key before adding it into + // application properties collection. + // https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-construct + string tokenValue = readCount == 0 ? null : value.Substring(position - readCount, readCount).Replace(QueryStringIdentifier, string.Empty); return new Token(tokenType, tokenValue); } diff --git a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs index 7afb17c35d..7931f3ff92 100644 --- a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs +++ b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs @@ -23,7 +23,6 @@ public partial class DeviceClient : IDisposable /// /// The telemetry message. /// A cancellation token to cancel the operation. - /// public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default) => InternalClient.SendTelemetryAsync(telemetryMessage, cancellationToken); @@ -37,5 +36,32 @@ public Task SubscribeToCommandsAsync( Func> callback, object userContext, CancellationToken cancellationToken = default) => InternalClient.SubscribeToCommandsAsync(callback, userContext, cancellationToken); + + /// + /// Retrieve the client properties. + /// + /// A cancellation token to cancel the operation. + /// The device properties. + public Task GetClientPropertiesAsync(CancellationToken cancellationToken = default) + => InternalClient.GetClientPropertiesAsync(cancellationToken); + + /// + /// Update the client properties. + /// This operation enables the partial update of the properties of the connected client. + /// + /// Reported properties to push. + /// A cancellation token to cancel the operation. + /// The response of the update operation. + public Task UpdateClientPropertiesAsync(ClientPropertyCollection propertyCollection, CancellationToken cancellationToken = default) + => InternalClient.UpdateClientPropertiesAsync(propertyCollection, cancellationToken); + + /// + /// Sets the global listener for writable property update events. + /// + /// The global call back to handle all writable property updates. + /// Generic parameter to be interpreted by the client code. + /// A cancellation token to cancel the operation. + public Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken = default) + => InternalClient.SubscribeToWritablePropertiesEventAsync(callback, userContext, cancellationToken); } } diff --git a/iothub/device/src/IDelegatingHandler.cs b/iothub/device/src/IDelegatingHandler.cs index 7b8cfa8918..3729e0ed47 100644 --- a/iothub/device/src/IDelegatingHandler.cs +++ b/iothub/device/src/IDelegatingHandler.cs @@ -66,5 +66,11 @@ internal interface IDelegatingHandler : IContinuationProvider GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken); + + Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken); } } diff --git a/iothub/device/src/InternalClient.ConventionBasedOperations.cs b/iothub/device/src/InternalClient.ConventionBasedOperations.cs index e92f0d7beb..4e785e96d0 100644 --- a/iothub/device/src/InternalClient.ConventionBasedOperations.cs +++ b/iothub/device/src/InternalClient.ConventionBasedOperations.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client @@ -62,5 +63,52 @@ internal Task SubscribeToCommandsAsync(Func GetClientPropertiesAsync(CancellationToken cancellationToken) + { + try + { + return await InnerHandler.GetPropertiesAsync(PayloadConvention, cancellationToken).ConfigureAwait(false); + } + catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) + { + cancellationToken.ThrowIfCancellationRequested(); + throw; + } + } + + internal async Task UpdateClientPropertiesAsync(ClientPropertyCollection clientProperties, CancellationToken cancellationToken) + { + if (clientProperties == null) + { + throw new ArgumentNullException(nameof(clientProperties)); + } + + try + { + clientProperties.Convention = PayloadConvention; + return await InnerHandler.SendPropertyPatchAsync(clientProperties, cancellationToken).ConfigureAwait(false); + } + catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) + { + cancellationToken.ThrowIfCancellationRequested(); + throw; + } + } + + internal Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken) + { + // Subscribe to DesiredPropertyUpdateCallback internally and use the callback received internally to invoke the user supplied Property callback. + var desiredPropertyUpdateCallback = new DesiredPropertyUpdateCallback((twinCollection, userContext) => + { + // convert a TwinCollection to PropertyCollection + var propertyCollection = ClientPropertyCollection.FromTwinCollection(twinCollection, PayloadConvention); + callback.Invoke(propertyCollection, userContext); + + return TaskHelpers.CompletedTask; + }); + + return SetDesiredPropertyUpdateCallbackAsync(desiredPropertyUpdateCallback, userContext, cancellationToken); + } } } diff --git a/iothub/device/src/MessageBase.cs b/iothub/device/src/MessageBase.cs index 11c491cc27..b9989d36b9 100644 --- a/iothub/device/src/MessageBase.cs +++ b/iothub/device/src/MessageBase.cs @@ -181,15 +181,6 @@ public DateTime CreationTimeUtc /// public bool IsSecurityMessage => CommonConstants.SecurityMessageInterfaceId.Equals(GetSystemProperty(MessageSystemPropertyNames.InterfaceId), StringComparison.Ordinal); - /// - /// Used to specify the content type of the message. - /// - internal string PayloadContentType - { - get => GetSystemProperty(MessageSystemPropertyNames.ContentType); - set => SystemProperties[MessageSystemPropertyNames.ContentType] = value; - } - /// /// Specifies the input name on which the message was sent, if there was one. /// @@ -217,20 +208,11 @@ public string ConnectionModuleId internal set => SystemProperties[MessageSystemPropertyNames.ConnectionModuleId] = value; } - /// - /// Used to specify the content encoding type of the message. - /// - internal string PayloadContentEncoding - { - get => GetSystemProperty(MessageSystemPropertyNames.ContentEncoding); - set => SystemProperties[MessageSystemPropertyNames.ContentEncoding] = value; - } - /// /// The DTDL component name from where the telemetry message has originated. /// This is relevant only for plug and play certified devices. /// - internal string ComponentName + public string ComponentName { get => GetSystemProperty(MessageSystemPropertyNames.ComponentName); set => SystemProperties[MessageSystemPropertyNames.ComponentName] = value; @@ -250,6 +232,24 @@ internal string ComponentName Justification = "Cannot remove public property on a public facing type")] public Stream BodyStream { get { return _bodyStream; } protected set { _bodyStream = value; } } + /// + /// Used to specify the content type of the message. + /// + internal string PayloadContentType + { + get => GetSystemProperty(MessageSystemPropertyNames.ContentType); + set => SystemProperties[MessageSystemPropertyNames.ContentType] = value; + } + + /// + /// Used to specify the content encoding type of the message. + /// + internal string PayloadContentEncoding + { + get => GetSystemProperty(MessageSystemPropertyNames.ContentEncoding); + set => SystemProperties[MessageSystemPropertyNames.ContentEncoding] = value; + } + /// /// For outgoing messages, contains the Mqtt topic that the message is being sent to /// For incoming messages, contains the Mqtt topic that the message arrived on diff --git a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs index d8e19cf128..d7e16690b5 100644 --- a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs +++ b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs @@ -23,7 +23,6 @@ public partial class ModuleClient : IDisposable /// /// The telemetry message. /// A cancellation token to cancel the operation. - /// public Task SendTelemetryAsync(TelemetryMessage telemetryMessage, CancellationToken cancellationToken = default) => InternalClient.SendTelemetryAsync(telemetryMessage, cancellationToken); @@ -37,5 +36,32 @@ public Task SubscribeToCommandsAsync( Func> callback, object userContext, CancellationToken cancellationToken = default) => InternalClient.SubscribeToCommandsAsync(callback, userContext, cancellationToken); + + /// + /// Retrieve the client properties. + /// + /// A cancellation token to cancel the operation. + /// The device properties. + public Task GetClientPropertiesAsync(CancellationToken cancellationToken = default) + => InternalClient.GetClientPropertiesAsync(cancellationToken); + + /// + /// Update the client properties. + /// This operation enables the partial update of the properties of the connected client. + /// + /// Reported properties to push. + /// A cancellation token to cancel the operation. + /// The response of the update operation. + public Task UpdateClientPropertiesAsync(ClientPropertyCollection propertyCollection, CancellationToken cancellationToken = default) + => InternalClient.UpdateClientPropertiesAsync(propertyCollection, cancellationToken); + + /// + /// Sets the global listener for writable property update events. + /// + /// The global call back to handle all writable property updates. + /// Generic parameter to be interpreted by the client code. + /// A cancellation token to cancel the operation. + public Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken = default) + => InternalClient.SubscribeToWritablePropertiesEventAsync(callback, userContext, cancellationToken); } } diff --git a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs index e4ec03001c..f1657f6b1e 100644 --- a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs +++ b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs @@ -521,6 +521,26 @@ private async Task DisposeMessageAsync(string lockToken, AmqpIotDisposeActions o #endregion Accept-Dispose + #region Convention-based operations + + public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + { + throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + + "Note that you can still retrieve a client's properties using the legacy DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + + "ModuleClient.GetTwinAsync(CancellationToken cancellationToken) operations, but the properties will not be formatted " + + "as per DTDL terminology."); + } + + public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + { + throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + + "Note that you can still retrieve a client's properties using the legacy DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + + "ModuleClient.GetTwinAsync(CancellationToken cancellationToken) operations, but the properties will not be formatted " + + "as per DTDL terminology."); + } + + #endregion Convention-based operations + #region Helpers private void TwinMessageListener(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default) diff --git a/iothub/device/src/Transport/DefaultDelegatingHandler.cs b/iothub/device/src/Transport/DefaultDelegatingHandler.cs index d239eaf125..19f9e4a4f5 100644 --- a/iothub/device/src/Transport/DefaultDelegatingHandler.cs +++ b/iothub/device/src/Transport/DefaultDelegatingHandler.cs @@ -202,6 +202,18 @@ public virtual Task DisableEventReceiveAsync(CancellationToken cancellationToken return InnerHandler?.DisableEventReceiveAsync(cancellationToken) ?? TaskHelpers.CompletedTask; } + public virtual Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + return InnerHandler?.GetPropertiesAsync(payloadConvention, cancellationToken) ?? Task.FromResult(null); + } + + public virtual Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + return InnerHandler?.SendPropertyPatchAsync(reportedProperties, cancellationToken) ?? Task.FromResult(null); + } + public virtual bool IsUsable => InnerHandler?.IsUsable ?? true; protected void ThrowIfDisposed() diff --git a/iothub/device/src/Transport/ErrorDelegatingHandler.cs b/iothub/device/src/Transport/ErrorDelegatingHandler.cs index ee6c9e2d5b..058f6ce00e 100644 --- a/iothub/device/src/Transport/ErrorDelegatingHandler.cs +++ b/iothub/device/src/Transport/ErrorDelegatingHandler.cs @@ -145,6 +145,16 @@ public override Task SendMethodResponseAsync(MethodResponseInternal methodRespon return ExecuteWithErrorHandlingAsync(() => base.SendMethodResponseAsync(methodResponse, cancellationToken)); } + public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + { + return ExecuteWithErrorHandlingAsync(() => base.GetPropertiesAsync(payloadConvention, cancellationToken)); + } + + public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + { + return ExecuteWithErrorHandlingAsync(() => base.SendPropertyPatchAsync(reportedProperties, cancellationToken)); + } + private static bool IsNetworkExceptionChain(Exception exceptionChain) { return exceptionChain.Unwind(true).Any(e => IsNetwork(e) && !IsTlsSecurity(e)); diff --git a/iothub/device/src/Transport/HttpTransportHandler.cs b/iothub/device/src/Transport/HttpTransportHandler.cs index 7917452398..9d7972d1b6 100644 --- a/iothub/device/src/Transport/HttpTransportHandler.cs +++ b/iothub/device/src/Transport/HttpTransportHandler.cs @@ -397,6 +397,16 @@ public override Task RejectAsync(string lockToken, CancellationToken cancellatio cancellationToken); } + public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + { + throw new NotImplementedException("Property operations are not supported over HTTP. Please use MQTT protocol instead."); + } + + public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + { + throw new NotImplementedException("Property operations are not supported over HTTP. Please use MQTT protocol instead."); + } + // This is for invoking methods from an edge module to another edge device or edge module. internal Task InvokeMethodAsync(MethodInvokeRequest methodInvokeRequest, Uri uri, CancellationToken cancellationToken) { diff --git a/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs b/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs index af9a82fa73..054dcf580f 100644 --- a/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs +++ b/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs @@ -168,7 +168,7 @@ public override async Task WriteAsync(IChannelHandlerContext context, object dat throw new IotHubCommunicationException("MQTT is disconnected."); } - if (data is Message message) + if (data is MessageBase message) { await SendMessageAsync(context, message).ConfigureAwait(true); return; @@ -839,7 +839,7 @@ private void ProcessPublish(IChannelHandlerContext context, PublishPacket packet Logging.Exit(this, context.Name, packet, nameof(ProcessPublish)); } - private async Task SendMessageAsync(IChannelHandlerContext context, Message message) + private async Task SendMessageAsync(IChannelHandlerContext context, MessageBase message) { if (Logging.IsEnabled) Logging.Enter(this, context.Name, message, nameof(SendMessageAsync)); @@ -1132,7 +1132,7 @@ private ushort GetNextPacketId() return ret; } - public async Task ComposePublishPacketAsync(IChannelHandlerContext context, Message message, QualityOfService qos, string topicName) + public async Task ComposePublishPacketAsync(IChannelHandlerContext context, MessageBase message, QualityOfService qos, string topicName) { if (Logging.IsEnabled) Logging.Enter(this, context.Name, topicName, nameof(ComposePublishPacketAsync)); @@ -1386,7 +1386,7 @@ public static async Task WriteMessageAsync(IChannelHandlerContext context, objec } } - internal static string PopulateMessagePropertiesFromMessage(string topicName, Message message) + internal static string PopulateMessagePropertiesFromMessage(string topicName, MessageBase message) { var systemProperties = new Dictionary(); foreach (KeyValuePair property in message.SystemProperties) diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs index 43ae6551e0..1ef3e235f3 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs @@ -88,6 +88,11 @@ internal sealed class MqttTransportHandler : TransportHandler, IMqttIotHubEventH private const string ReceiveEventMessagePatternFilter = "devices/{0}/modules/{1}/#"; private const string ReceiveEventMessagePrefixPattern = "devices/{0}/modules/{1}/"; + // Identifiers for property update operations. + public const string VersionKey = "$version"; + + public const string RequestIdKey = "$rid"; + private static readonly int s_generationPrefixLength = Guid.NewGuid().ToString().Length; private static readonly Lazy s_eventLoopGroup = new Lazy(GetEventLoopGroup); private static readonly TimeSpan s_regexTimeoutMilliseconds = TimeSpan.FromMilliseconds(500); @@ -972,6 +977,56 @@ public override async Task SendTwinPatchAsync(TwinCollection reportedProperties, await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); } + public override async Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + EnsureValidState(); + + using var request = new Message(); + string rid = Guid.NewGuid().ToString(); + request.MqttTopicName = TwinGetTopic.FormatInvariant(rid); + + using Message response = await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); + + using var reader = new StreamReader(response.GetBodyStream(), payloadConvention.PayloadEncoder.ContentEncoding); + string body = reader.ReadToEnd(); + + try + { + ClientTwinProperties twinProperties = JsonConvert.DeserializeObject(body); + var properties = twinProperties.ToClientProperties(payloadConvention); + return properties; + } + catch (JsonReaderException ex) + { + if (Logging.IsEnabled) + Logging.Error(this, $"Failed to parse Twin JSON: {ex}. Message body: '{body}'"); + + throw; + } + } + + public override async Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + EnsureValidState(); + + byte[] body = reportedProperties.GetPayloadObjectBytes(); + using var bodyStream = new MemoryStream(body); + + using var request = new Message(bodyStream); + + string rid = Guid.NewGuid().ToString(); + request.MqttTopicName = TwinPatchTopic.FormatInvariant(rid); + + using Message message = await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); + return new ClientPropertiesUpdateResponse + { + RequestId = message.Properties[RequestIdKey], + Version = long.Parse(message.Properties[VersionKey], CultureInfo.InvariantCulture) + }; + } + private async Task OpenInternalAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -1098,17 +1153,15 @@ private Task SubscribeTwinResponsesAsync() QualityOfService.AtMostOnce))); } - private bool ParseResponseTopic(string topicName, out string rid, out int status) + private bool ParseResponseTopic(string topicName, out int status) { Match match = _twinResponseTopicRegex.Match(topicName); if (match.Success) { status = Convert.ToInt32(match.Groups[1].Value, CultureInfo.InvariantCulture); - rid = HttpUtility.ParseQueryString(match.Groups[2].Value).Get("$rid"); return true; } - rid = ""; status = 500; return false; } @@ -1125,9 +1178,9 @@ private async Task SendTwinRequestAsync(Message request, string rid, Ca { try { - if (ParseResponseTopic(possibleResponse.MqttTopicName, out string receivedRid, out int status)) + if (ParseResponseTopic(possibleResponse.MqttTopicName, out int status)) { - if (rid == receivedRid) + if (rid == possibleResponse.Properties[RequestIdKey]) { if (status >= 300) { diff --git a/iothub/device/src/Transport/RetryDelegatingHandler.cs b/iothub/device/src/Transport/RetryDelegatingHandler.cs index c3d0c2285b..35ead90d7b 100644 --- a/iothub/device/src/Transport/RetryDelegatingHandler.cs +++ b/iothub/device/src/Transport/RetryDelegatingHandler.cs @@ -606,6 +606,50 @@ await _internalRetryPolicy } } + public override async Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + { + try + { + Logging.Enter(this, payloadConvention, cancellationToken, nameof(SendPropertyPatchAsync)); + + return await _internalRetryPolicy + .ExecuteAsync( + async () => + { + await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); + return await base.GetPropertiesAsync(payloadConvention, cancellationToken).ConfigureAwait(false); + }, + cancellationToken) + .ConfigureAwait(false); + } + finally + { + Logging.Exit(this, payloadConvention, cancellationToken, nameof(SendPropertyPatchAsync)); + } + } + + public override async Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + { + try + { + Logging.Enter(this, reportedProperties, cancellationToken, nameof(SendPropertyPatchAsync)); + + return await _internalRetryPolicy + .ExecuteAsync( + async () => + { + await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); + return await base.SendPropertyPatchAsync(reportedProperties, cancellationToken).ConfigureAwait(false); + }, + cancellationToken) + .ConfigureAwait(false); + } + finally + { + Logging.Exit(this, reportedProperties, cancellationToken, nameof(SendPropertyPatchAsync)); + } + } + public override Task OpenAsync(CancellationToken cancellationToken) { return EnsureOpenedAsync(cancellationToken); diff --git a/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs b/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs index f56ad26c01..87275156ea 100644 --- a/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs +++ b/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information +// Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Diagnostics; @@ -37,6 +37,7 @@ public class MqttTransportHandlerTests private const int statusFailure = 400; private const string fakeResponseId = "fakeResponseId"; private static readonly TimeSpan ReceiveTimeoutBuffer = TimeSpan.FromSeconds(5); + private delegate bool MessageMatcher(Message msg); [TestMethod] @@ -369,7 +370,13 @@ public async Task MqttTransportHandlerSendTwinGetAsyncHappyPath() .Returns(msg => { var response = new Message(twinByteStream); - response.MqttTopicName = GetResponseTopic(msg.Arg().MqttTopicName, statusSuccess); + string mqttTopic = GetResponseTopic(msg.Arg().MqttTopicName, statusSuccess); + response.MqttTopicName = mqttTopic; + var publishPacket = new PublishPacket(QualityOfService.AtMostOnce, false, false) + { + TopicName = mqttTopic + }; + MqttIotHubAdapter.PopulateMessagePropertiesFromPacket(response, publishPacket); transport.OnMessageReceived(response); return TaskHelpers.CompletedTask; }); @@ -393,7 +400,13 @@ public async Task MqttTransportHandlerSendTwinGetAsyncReturnsFailure() .Returns(msg => { var response = new Message(); - response.MqttTopicName = GetResponseTopic(msg.Arg().MqttTopicName, statusFailure); + string mqttTopic = GetResponseTopic(msg.Arg().MqttTopicName, statusFailure); + response.MqttTopicName = mqttTopic; + var publishPacket = new PublishPacket(QualityOfService.AtMostOnce, false, false) + { + TopicName = mqttTopic + }; + MqttIotHubAdapter.PopulateMessagePropertiesFromPacket(response, publishPacket); transport.OnMessageReceived(response); return TaskHelpers.CompletedTask; }); @@ -431,8 +444,8 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncHappyPath() // arrange var transport = CreateTransportHandlerWithMockChannel(out IChannel channel); var props = new TwinCollection(); - string receivedBody = null; props["foo"] = "bar"; + string receivedBody = null; channel .WriteAndFlushAsync(Arg.Is(msg => msg.MqttTopicName.StartsWith(twinPatchReportedTopicPrefix))) .Returns(msg => @@ -442,7 +455,13 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncHappyPath() receivedBody = reader.ReadToEnd(); var response = new Message(); - response.MqttTopicName = GetResponseTopic(request.MqttTopicName, statusSuccess); + string mqttTopic = GetResponseTopic(request.MqttTopicName, statusSuccess); + response.MqttTopicName = mqttTopic; + var publishPacket = new PublishPacket(QualityOfService.AtMostOnce, false, false) + { + TopicName = mqttTopic + }; + MqttIotHubAdapter.PopulateMessagePropertiesFromPacket(response, publishPacket); transport.OnMessageReceived(response); return TaskHelpers.CompletedTask; @@ -470,7 +489,13 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncReturnsFailure() { var request = msg.Arg(); var response = new Message(); - response.MqttTopicName = GetResponseTopic(request.MqttTopicName, statusFailure); + string mqttTopic = GetResponseTopic(request.MqttTopicName, statusFailure); + response.MqttTopicName = mqttTopic; + var publishPacket = new PublishPacket(QualityOfService.AtMostOnce, false, false) + { + TopicName = mqttTopic + }; + MqttIotHubAdapter.PopulateMessagePropertiesFromPacket(response, publishPacket); transport.OnMessageReceived(response); return TaskHelpers.CompletedTask; }); diff --git a/shared/src/NewtonsoftJsonPayloadSerializer.cs b/shared/src/NewtonsoftJsonPayloadSerializer.cs index ec0b10cef0..7d90b163a0 100644 --- a/shared/src/NewtonsoftJsonPayloadSerializer.cs +++ b/shared/src/NewtonsoftJsonPayloadSerializer.cs @@ -43,7 +43,7 @@ public override T ConvertFromObject(object objectToConvert) { return default; } - return ((JObject)objectToConvert).ToObject(); + return ((JToken)objectToConvert).ToObject(); } /// diff --git a/shared/src/StatusCodes.cs b/shared/src/StatusCodes.cs new file mode 100644 index 0000000000..a07bba5d10 --- /dev/null +++ b/shared/src/StatusCodes.cs @@ -0,0 +1,33 @@ +// 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.Shared +{ + /// + /// A list of common status codes to represent the response from the client. + /// + /// + /// These status codes are based on the HTTP status codes listed here + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "To allow customers to extend this class we need to not mark it static.")] + public class StatusCodes + { + /// + /// Status code 200. + /// + public static int OK => 200; + /// + /// Status code 202. + /// + public static int Accepted => 202; + /// + /// Status code 400. + /// + public static int BadRequest => 400; + /// + /// Status code 404. + /// + public static int NotFound => 404; + } +} From fda54b8188a99fa9e54287f73d8af454ab438e3e Mon Sep 17 00:00:00 2001 From: jamdavi <73593426+jamdavi@users.noreply.github.com> Date: Fri, 21 May 2021 21:53:13 -0400 Subject: [PATCH 05/77] feat(e2e-tests): Add telemetry E2E tests --- .../iothub/telemetry/TelemetrySendE2ETests.cs | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs diff --git a/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs b/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs new file mode 100644 index 0000000000..5eed7aab2c --- /dev/null +++ b/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs @@ -0,0 +1,130 @@ +// 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.Net; +using System.Threading.Tasks; +using Microsoft.Azure.Devices.Client; +using Microsoft.Azure.Devices.E2ETests.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.E2ETests.Telemetry +{ + [TestClass] + [TestCategory("E2E")] + [TestCategory("IoTHub")] + public partial class TelemetrySendE2ETests : E2EMsTestBase + { + private readonly string DevicePrefix = $"{nameof(TelemetrySendE2ETests)}_"; + private readonly string ModulePrefix = $"{nameof(TelemetrySendE2ETests)}_"; + + [LoggedTestMethod] + public async Task Telemetry_DeviceSendSingleTelemetry_Mqtt() + { + await SendTelemetryAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_Tcp_Only).ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Telemetry_DeviceSendSingleTelemetry_MqttWs() + { + await SendTelemetryAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_WebSocket_Only).ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Telemetry_DeviceSendSingleTelemetryWithComponent_Mqtt() + { + await SendTelemetryWithComponentAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_Tcp_Only).ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Telemetry_DeviceSendSingleTelemetryWithComponent_MqttWs() + { + await SendTelemetryWithComponentAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_WebSocket_Only).ConfigureAwait(false); + } + + private async Task SendTelemetryAsync(TestDeviceType type, Client.TransportType transport) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, type).ConfigureAwait(false); + using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); + + await deviceClient.OpenAsync().ConfigureAwait(false); + await SendSingleMessageAsync(deviceClient, testDevice.Id, Logger).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); + } + + private async Task SendTelemetryWithComponentAsync(TestDeviceType type, Client.TransportType transport) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, type).ConfigureAwait(false); + using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); + + await deviceClient.OpenAsync().ConfigureAwait(false); + await SendSingleMessageWithComponentAsync(deviceClient, testDevice.Id, Logger).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); + } + + public static async Task SendSingleMessageAsync(DeviceClient deviceClient, string deviceId, MsTestLogger logger) + { + Client.TelemetryMessage testMessage; + (testMessage, _) = ComposeTelemetryMessage(logger); + + using (testMessage) + { + await deviceClient.SendTelemetryAsync(testMessage).ConfigureAwait(false); + } + } + + public static async Task SendSingleMessageWithComponentAsync(DeviceClient deviceClient, string deviceId, MsTestLogger logger) + { + Client.TelemetryMessage testMessage; + (testMessage, _) = ComposeTelemetryMessageWithComponent(logger); + + using (testMessage) + { + await deviceClient.SendTelemetryAsync(testMessage).ConfigureAwait(false); + } + } + + public static (Client.TelemetryMessage message, string p1Value) ComposeTelemetryMessageWithComponent(MsTestLogger logger) + { + string messageId = Guid.NewGuid().ToString(); + string p1Value = Guid.NewGuid().ToString(); + string userId = Guid.NewGuid().ToString(); + string componentName = Guid.NewGuid().ToString(); + + logger.Trace($"{nameof(ComposeTelemetryMessageWithComponent)}: messageId='{messageId}' userId='{userId}' p1Value='{p1Value}'"); + var message = new TelemetryMessage + { + MessageId = messageId, + UserId = userId, + ComponentName = componentName, + Telemetry = { + { "property1", p1Value }, + { "property2", null}, + } + }; + + return (message, p1Value); + } + + public static (Client.TelemetryMessage message, string p1Value) ComposeTelemetryMessage(MsTestLogger logger) + { + string messageId = Guid.NewGuid().ToString(); + string p1Value = Guid.NewGuid().ToString(); + string userId = Guid.NewGuid().ToString(); + + logger.Trace($"{nameof(ComposeTelemetryMessage)}: messageId='{messageId}' userId='{userId}' p1Value='{p1Value}'"); + var message = new TelemetryMessage + { + MessageId = messageId, + UserId = userId, + Telemetry = { + { "property1", p1Value }, + { "property2", null}, + } + }; + + return (message, p1Value); + } + + } +} From 531f2043069a6a60b636451b70eb8cc36ade0a70 Mon Sep 17 00:00:00 2001 From: jamdavi <73593426+jamdavi@users.noreply.github.com> Date: Fri, 21 May 2021 23:30:42 -0400 Subject: [PATCH 06/77] feat(e2e-tests): Add command E2E tests --- e2e/test/iothub/command/CommandE2ETests.cs | 198 +++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 e2e/test/iothub/command/CommandE2ETests.cs diff --git a/e2e/test/iothub/command/CommandE2ETests.cs b/e2e/test/iothub/command/CommandE2ETests.cs new file mode 100644 index 0000000000..07606a5225 --- /dev/null +++ b/e2e/test/iothub/command/CommandE2ETests.cs @@ -0,0 +1,198 @@ +// 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.Diagnostics.Tracing; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Azure.Devices.Client; +using Microsoft.Azure.Devices.Common.Exceptions; +using Microsoft.Azure.Devices.E2ETests.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.E2ETests.Commands +{ + public class ServiceCommandRequestAssertion + { + public int A => 123; + } + + public class ServiceCommandRequestObject + { + public int A { get; set; } + } + + public class DeviceCommandResponse + { + public string Name => "e2e_test"; + } + + [TestClass] + [TestCategory("E2E")] + [TestCategory("IoTHub")] + public class CommandE2ETests : E2EMsTestBase + { + public const string ComponentName = "testableComponent"; + + private readonly string _devicePrefix = $"E2E_{nameof(CommandE2ETests)}_"; + private readonly string _modulePrefix = $"E2E_{nameof(CommandE2ETests)}_"; + private const string CommandName = "CommandE2ETest"; + + private static readonly TimeSpan s_defaultCommandTimeoutMinutes = TimeSpan.FromMinutes(1); + + [LoggedTestMethod] + public async Task Command_DeviceReceivesCommandAndResponse_Mqtt() + { + await SendCommandAndRespondAsync(Client.TransportType.Mqtt_Tcp_Only, SetDeviceReceiveCommandAsync).ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Command_DeviceReceivesCommandAndResponse_MqttWs() + { + await SendCommandAndRespondAsync(Client.TransportType.Mqtt_WebSocket_Only, SetDeviceReceiveCommandAsync).ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Command_DeviceReceivesCommandAndResponseWithComponent_Mqtt() + { + await SendCommandAndRespondAsync(Client.TransportType.Mqtt_Tcp_Only, SetDeviceReceiveCommandAsync, withComponent: true).ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Command_DeviceReceivesCommandAndResponseWithComponent_MqttWs() + { + await SendCommandAndRespondAsync(Client.TransportType.Mqtt_WebSocket_Only, SetDeviceReceiveCommandAsync, withComponent: true).ConfigureAwait(false); + } + + public static async Task DigitalTwinsSendCommandAndVerifyResponseAsync(string deviceId, string componentName, string commandName, MsTestLogger logger) + { + string payloadToSend = JsonConvert.SerializeObject(new ServiceCommandRequestObject { A = 123 }); + string responseExpected = JsonConvert.SerializeObject(new DeviceCommandResponse()); + string payloadReceived = string.Empty; + int statusCode = 0; +#if NET451 + + ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + + logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Invoke command {commandName}."); + + CloudToDeviceMethodResult serviceClientResponse = null; + if (string.IsNullOrEmpty(componentName)) + { + var serviceCommand = new CloudToDeviceMethod(commandName).SetPayloadJson(payloadToSend); + serviceClientResponse = + await serviceClient.InvokeDeviceMethodAsync( + deviceId, serviceCommand).ConfigureAwait(false); + } + else + { + var serviceCommand = new CloudToDeviceMethod($"{componentName}*{commandName}").SetPayloadJson(payloadToSend); + serviceClientResponse = + await serviceClient.InvokeDeviceMethodAsync( + deviceId, serviceCommand).ConfigureAwait(false); + } + + statusCode = serviceClientResponse.Status; + payloadReceived = serviceClientResponse.GetPayloadAsJson(); + + serviceClient.Dispose(); +#else + DigitalTwinClient digitalTwinClient = DigitalTwinClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + + logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Invoke command {commandName}."); + + Rest.HttpOperationResponse response = null; + if (string.IsNullOrEmpty(componentName)) + { + response = + await digitalTwinClient.InvokeCommandAsync( + deviceId, + commandName, + payloadToSend).ConfigureAwait(false); + } + else + { + response = + await digitalTwinClient.InvokeComponentCommandAsync( + deviceId, + componentName, + commandName, + payloadToSend).ConfigureAwait(false); + } + + statusCode = (int)response.Response.StatusCode; + payloadReceived = response.Body.Payload; + + digitalTwinClient.Dispose(); +#endif + logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Command status: {statusCode}."); + Assert.AreEqual(200, statusCode, $"The expected response status should be 200 but was {statusCode}"); + Assert.AreEqual(responseExpected, payloadReceived, $"The expected response payload should be {responseExpected} but was {payloadReceived}"); + + } + + public static async Task SetDeviceReceiveCommandAsync(DeviceClient deviceClient, string componentName, string commandName, MsTestLogger logger) + { + var commandCallReceived = new TaskCompletionSource(); + + await deviceClient.SubscribeToCommandsAsync( + (request, context) => + { + logger.Trace($"{nameof(SetDeviceReceiveCommandAsync)}: DeviceClient command: {request.CommandName}."); + + try + { + var valueToTest = request.GetData(); + if (string.IsNullOrEmpty(componentName)) + { + Assert.AreEqual(null, request.ComponentName, $"The expected component name should be null but was {request.ComponentName}"); + } + else + { + Assert.AreEqual(componentName, request.ComponentName, $"The expected component name should be {componentName} but was {request.ComponentName}"); + + } + var assertionObject = new ServiceCommandRequestAssertion(); + string responseExpected = JsonConvert.SerializeObject(assertionObject); + Assert.AreEqual(responseExpected, request.DataAsJson, $"The expected response payload should be {responseExpected} but was {request.DataAsJson}"); + Assert.AreEqual(assertionObject.A, valueToTest.A, $"The expected response object did not decode properly. Value a should be {assertionObject.A} but was {valueToTest?.A ?? int.MinValue}"); + commandCallReceived.SetResult(true); + } + catch (Exception ex) + { + commandCallReceived.SetException(ex); + } + + return Task.FromResult(new CommandResponse(new DeviceCommandResponse(), 200)); + }, + null).ConfigureAwait(false); + + // Return the task that tells us we have received the callback. + return commandCallReceived.Task; + } + + private async Task SendCommandAndRespondAsync(Client.TransportType transport, Func> setDeviceReceiveCommand, bool withComponent = false) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + string componentName = null; + if (withComponent) + { + componentName = ComponentName; + } + + Task commandReceivedTask = await setDeviceReceiveCommand(deviceClient, componentName, CommandName, Logger).ConfigureAwait(false); + + await Task + .WhenAll( + DigitalTwinsSendCommandAndVerifyResponseAsync(testDevice.Id, componentName, CommandName, Logger), + commandReceivedTask) + .ConfigureAwait(false); + + await deviceClient.CloseAsync().ConfigureAwait(false); + } + } +} \ No newline at end of file From e2d88e468e6453ac4cd630f29f82f9b01837e084 Mon Sep 17 00:00:00 2001 From: jamdavi <73593426+jamdavi@users.noreply.github.com> Date: Mon, 24 May 2021 16:12:46 -0400 Subject: [PATCH 07/77] fix(iot-device): Updating client property collection to handle no convention --- iothub/device/src/ClientPropertyCollection.cs | 26 +++++++++++++++++-- iothub/device/src/PayloadCollection.cs | 6 +++++ shared/src/SystemTextJsonPayloadSerializer.cs | 3 +-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index df9e17dbad..cd6737e15a 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -283,6 +283,10 @@ public bool Contains(string componentName, string propertyName) { if (!string.IsNullOrEmpty(componentName) && Collection.TryGetValue(componentName, out var component)) { + if (component is IDictionary nestedDictionary) + { + return nestedDictionary.TryGetValue(propertyName, out var _); + } return Convention.PayloadSerializer.TryGetNestedObjectValue(component, propertyName, out _); } return Collection.TryGetValue(propertyName, out _); @@ -307,11 +311,29 @@ public bool Contains(string componentName, string propertyName) /// true if the property collection contains a component level property with the specified key; otherwise, false. public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue) { + if (Logging.IsEnabled && Convention == null) + { + Logging.Info(this, $"The convention for this collection is not set; this typically means this collection was not created by the client. " + + $"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue)); + } + if (Contains(componentName, propertyName)) { object componentProperties = Collection[componentName]; - Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); - return true; + + if (componentProperties is IDictionary nestedDictionary) + { + if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement) && dictionaryElement is T valueRef) + { + propertyValue = valueRef; + return true; + } + } + else + { + Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); + return true; + } } propertyValue = default; diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index b417db2ee6..4710157b06 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -100,6 +100,12 @@ public bool Contains(string key) /// True if the collection contains an element with the specified key; otherwise, it returns false. public bool TryGetValue(string key, out T value) { + if (Logging.IsEnabled && Convention == null) + { + Logging.Info(this, $"The convention for this collection is not set; this typically means this collection was not created by the client. " + + $"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue)); + } + if (Collection.ContainsKey(key)) { // If the object is of type T go ahead and return it. diff --git a/shared/src/SystemTextJsonPayloadSerializer.cs b/shared/src/SystemTextJsonPayloadSerializer.cs index 8a5ab3f235..31602cbc6e 100644 --- a/shared/src/SystemTextJsonPayloadSerializer.cs +++ b/shared/src/SystemTextJsonPayloadSerializer.cs @@ -4,9 +4,8 @@ #if !NET451 using System.Text.Json; -using Microsoft.Azure.Devices.Shared; -namespace Microsoft.Azure.Devices.Client.Samples +namespace Microsoft.Azure.Devices.Shared { /// /// A implementation. From ecaaaeb37f95c6d7147b70c768e1de0fc1a1d720 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 24 May 2021 17:20:05 -0700 Subject: [PATCH 08/77] feat(samples): Add thermostat and temperature controller sample --- azureiot.sln | 20 + .../Models/DeviceInformation.json | 64 +++ .../Models/TemperatureController.json | 83 ++++ .../Models/Thermostat.json | 89 ++++ .../Models/Thermostat2.json | 89 ++++ .../TemperatureController/Parameter.cs | 86 ++++ .../TemperatureController/Program.cs | 167 +++++++ .../Properties/launchSettings.template.json | 22 + .../SystemTextJsonPayloadConvention.cs | 19 + .../SystemTextJsonPayloadSerializer.cs | 9 +- .../TemperatureController.csproj | 19 + .../TemperatureControllerSample.cs | 407 ++++++++++++++++++ .../TemperatureReport.cs | 26 ++ .../Thermostat/Models/Thermostat.json | 89 ++++ .../Thermostat/Parameter.cs | 86 ++++ .../Thermostat/Program.cs | 162 +++++++ .../Properties/launchSettings.template.json | 22 + .../Thermostat/TemperatureReport.cs | 26 ++ .../Thermostat/Thermostat.csproj | 19 + .../Thermostat/ThermostatSample.cs | 200 +++++++++ .../convention-based-samples/readme.md | 58 +++ .../Transport/Amqp/AmqpTransportHandler.cs | 4 +- 22 files changed, 1758 insertions(+), 8 deletions(-) create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Program.cs create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs rename {shared/src => iothub/device/samples/convention-based-samples/TemperatureController}/SystemTextJsonPayloadSerializer.cs (93%) create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs create mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Program.cs create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj create mode 100644 iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs create mode 100644 iothub/device/samples/convention-based-samples/readme.md diff --git a/azureiot.sln b/azureiot.sln index dd21735d2b..5b4d4d37ae 100644 --- a/azureiot.sln +++ b/azureiot.sln @@ -77,6 +77,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Shared.Tests", "shared\tests\Microsoft.Azure.Devices.Shared.Tests.csproj", "{CEEE435F-32FC-4DE5-8735-90F6AC950A01}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{2368415A-9C09-4F47-9636-FDCA4B85C88C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "convention-based-samples", "convention-based-samples", "{22318FE4-1453-41BF-A38D-9401C4F16023}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thermostat", "iothub\device\samples\convention-based-samples\Thermostat\Thermostat.csproj", "{5658A5DF-EDEF-4561-9F0B-A37EEABC8135}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemperatureController", "iothub\device\samples\convention-based-samples\TemperatureController\TemperatureController.csproj", "{B557FCFE-015C-4A65-81B6-B4987E07BFB7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -150,6 +158,14 @@ Global {CEEE435F-32FC-4DE5-8735-90F6AC950A01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CEEE435F-32FC-4DE5-8735-90F6AC950A01}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEEE435F-32FC-4DE5-8735-90F6AC950A01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Release|Any CPU.Build.0 = Release|Any CPU + {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -184,6 +200,10 @@ Global {275DEE86-1EEA-47C4-A9C5-797DF20EC8A7} = {3AA089A9-A035-439E-BAF6-C3975A334379} {8E25CDE3-992D-4942-8C38-51A0D8E8EB70} = {9C260BF0-1CCA-45A2-AAB8-6419291B8B88} {CEEE435F-32FC-4DE5-8735-90F6AC950A01} = {3AA089A9-A035-439E-BAF6-C3975A334379} + {2368415A-9C09-4F47-9636-FDCA4B85C88C} = {A48437BA-3C5B-431E-9B2F-96C850E9E1A5} + {22318FE4-1453-41BF-A38D-9401C4F16023} = {2368415A-9C09-4F47-9636-FDCA4B85C88C} + {5658A5DF-EDEF-4561-9F0B-A37EEABC8135} = {22318FE4-1453-41BF-A38D-9401C4F16023} + {B557FCFE-015C-4A65-81B6-B4987E07BFB7} = {22318FE4-1453-41BF-A38D-9401C4F16023} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF61665D-340A-494B-9705-571456BDC752} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json new file mode 100644 index 0000000000..6d59180dc8 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json @@ -0,0 +1,64 @@ +{ + "@id": "dtmi:azure:DeviceManagement:DeviceInformation;1", + "@type": "Interface", + "displayName": "Device Information", + "contents": [ + { + "@type": "Property", + "name": "manufacturer", + "displayName": "Manufacturer", + "schema": "string", + "description": "Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso." + }, + { + "@type": "Property", + "name": "model", + "displayName": "Device model", + "schema": "string", + "description": "Device model name or ID. Ex. Surface Book 2." + }, + { + "@type": "Property", + "name": "swVersion", + "displayName": "Software version", + "schema": "string", + "description": "Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45" + }, + { + "@type": "Property", + "name": "osName", + "displayName": "Operating system name", + "schema": "string", + "description": "Name of the operating system on the device. Ex. Windows 10 IoT Core." + }, + { + "@type": "Property", + "name": "processorArchitecture", + "displayName": "Processor architecture", + "schema": "string", + "description": "Architecture of the processor on the device. Ex. x64 or ARM." + }, + { + "@type": "Property", + "name": "processorManufacturer", + "displayName": "Processor manufacturer", + "schema": "string", + "description": "Name of the manufacturer of the processor on the device. Ex. Intel." + }, + { + "@type": "Property", + "name": "totalStorage", + "displayName": "Total storage", + "schema": "double", + "description": "Total available storage on the device in kilobytes. Ex. 2048000 kilobytes." + }, + { + "@type": "Property", + "name": "totalMemory", + "displayName": "Total memory", + "schema": "double", + "description": "Total available memory on the device in kilobytes. Ex. 256000 kilobytes." + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json new file mode 100644 index 0000000000..997b5cd34a --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json @@ -0,0 +1,83 @@ +{ + "@context": [ + "dtmi:iotcentral:context;2", + "dtmi:dtdl:context;2" + ], + "@id": "dtmi:com:example:TemperatureController;2", + "@type": "Interface", + "contents": [ + { + "@type": [ + "Telemetry", + "DataSize" + ], + "description": { + "en": "Current working set of the device memory in KiB." + }, + "displayName": { + "en": "Working Set" + }, + "name": "workingSet", + "schema": "double", + "unit": "kibibit" + }, + { + "@type": "Property", + "displayName": { + "en": "Serial Number" + }, + "name": "serialNumber", + "schema": "string", + "writable": false + }, + { + "@type": "Command", + "commandType": "synchronous", + "description": { + "en": "Reboots the device after waiting the number of seconds specified." + }, + "displayName": { + "en": "Reboot" + }, + "name": "reboot", + "request": { + "@type": "CommandPayload", + "description": { + "en": "Number of seconds to wait before rebooting the device." + }, + "displayName": { + "en": "Delay" + }, + "name": "delay", + "schema": "integer" + } + }, + { + "@type": "Component", + "displayName": { + "en": "thermostat1" + }, + "name": "thermostat1", + "schema": "dtmi:com:example:Thermostat;1" + }, + { + "@type": "Component", + "displayName": { + "en": "thermostat2" + }, + "name": "thermostat2", + "schema": "dtmi:com:example:Thermostat;2" + }, + { + "@type": "Component", + "displayName": { + "en": "DeviceInfo" + }, + "name": "deviceInformation", + "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1" + } + ], + "displayName": { + "en": "Temperature Controller" + } + } \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json new file mode 100644 index 0000000000..46b21f85cb --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json @@ -0,0 +1,89 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:Thermostat;1", + "@type": "Interface", + "displayName": "Thermostat", + "description": "Reports current temperature and provides desired temperature control.", + "contents": [ + { + "@type": [ + "Telemetry", + "Temperature" + ], + "name": "temperature", + "displayName" : "Temperature", + "description" : "Temperature in degrees Celsius.", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "targetTemperature", + "schema": "double", + "displayName": "Target Temperature", + "description": "Allows to remotely specify the desired target temperature.", + "unit" : "degreeCelsius", + "writable": true + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "maxTempSinceLastReboot", + "schema": "double", + "unit" : "degreeCelsius", + "displayName": "Max temperature since last reboot.", + "description": "Returns the max temperature since last device reboot." + }, + { + "@type": "Command", + "name": "getMaxMinReport", + "displayName": "Get Max-Min report.", + "description": "This command returns the max, min and average temperature from the specified time to the current time.", + "request": { + "name": "since", + "displayName": "Since", + "description": "Period to return the max-min report.", + "schema": "dateTime" + }, + "response": { + "name" : "tempReport", + "displayName": "Temperature Report", + "schema": { + "@type": "Object", + "fields": [ + { + "name": "maxTemp", + "displayName": "Max temperature", + "schema": "double" + }, + { + "name": "minTemp", + "displayName": "Min temperature", + "schema": "double" + }, + { + "name" : "avgTemp", + "displayName": "Average Temperature", + "schema": "double" + }, + { + "name" : "startTime", + "displayName": "Start Time", + "schema": "dateTime" + }, + { + "name" : "endTime", + "displayName": "End Time", + "schema": "dateTime" + } + ] + } + } + } + ] +} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json new file mode 100644 index 0000000000..2309b4a5d4 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json @@ -0,0 +1,89 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:Thermostat;2", + "@type": "Interface", + "displayName": "Thermostat", + "description": "Reports current temperature and provides desired temperature control.", + "contents": [ + { + "@type": [ + "Telemetry", + "Temperature" + ], + "name": "temperature", + "displayName" : "Temperature", + "description" : "Temperature in degrees Celsius.", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "targetTemperature", + "schema": "double", + "displayName": "Target Temperature", + "description": "Allows to remotely specify the desired target temperature.", + "unit" : "degreeCelsius", + "writable": true + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "maxTempSinceLastReboot", + "schema": "double", + "unit" : "degreeCelsius", + "displayName": "Max temperature since last reboot.", + "description": "Returns the max temperature since last device reboot." + }, + { + "@type": "Command", + "name": "getMaxMinReport", + "displayName": "Get Max-Min report.", + "description": "This command returns the max, min and average temperature from the specified time to the current time.", + "request": { + "name": "since", + "displayName": "Since", + "description": "Period to return the max-min report.", + "schema": "dateTime" + }, + "response": { + "name" : "tempReport", + "displayName": "Temperature Report", + "schema": { + "@type": "Object", + "fields": [ + { + "name": "maxTemp", + "displayName": "Max temperature", + "schema": "double" + }, + { + "name": "minTemp", + "displayName": "Min temperature", + "schema": "double" + }, + { + "name" : "avgTemp", + "displayName": "Average Temperature", + "schema": "double" + }, + { + "name" : "startTime", + "displayName": "Start Time", + "schema": "dateTime" + }, + { + "name" : "endTime", + "displayName": "End Time", + "schema": "dateTime" + } + ] + } + } + } + ] +} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs b/iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs new file mode 100644 index 0000000000..9f8f5b99ff --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using CommandLine; +using Microsoft.Extensions.Logging; +using System; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + /// + /// Parameters for the application supplied via command line arguments. + /// If the parameter is not supplied via command line args, it will look for it in environment variables. + /// + internal class Parameters + { + [Option( + "DeviceSecurityType", + HelpText = "(Required) The flow that will be used for connecting the device for the sample. Possible case-insensitive values include: dps, connectionString." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\".")] + public string DeviceSecurityType { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_SECURITY_TYPE"); + + [Option( + 'p', + "PrimaryConnectionString", + HelpText = "(Required if DeviceSecurityType is \"connectionString\"). \nThe primary connection string for the device to simulate." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_CONNECTION_STRING\".")] + public string PrimaryConnectionString { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_CONNECTION_STRING"); + + [Option( + 'e', + "DpsEndpoint", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS endpoint to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ENDPOINT\".")] + public string DpsEndpoint { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ENDPOINT"); + + [Option( + 'i', + "DpsIdScope", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS ID Scope to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ID_SCOPE\".")] + public string DpsIdScope { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ID_SCOPE"); + + [Option( + 'd', + "DeviceId", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device registration Id to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_ID\".")] + public string DeviceId { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_ID"); + + [Option( + 'k', + "DeviceSymmetricKey", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device symmetric key to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_KEY\".")] + public string DeviceSymmetricKey { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_KEY"); + + [Option( + 'r', + "Application running time (in seconds)", + Required = false, + HelpText = "The running time for this console application. Leave it unassigned to run the application until it is explicitly canceled using Control+C.")] + public double? ApplicationRunningTime { get; set; } + + public bool Validate(ILogger logger) + { + if (string.IsNullOrWhiteSpace(DeviceSecurityType)) + { + logger.LogWarning("Device provisioning type not set, please set the environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\"" + + "or pass in \"-s | --DeviceSecurityType\" through command line. \nWill default to using \"dps\" flow."); + + DeviceSecurityType = "dps"; + } + + return (DeviceSecurityType.ToLowerInvariant()) switch + { + "dps" => !string.IsNullOrWhiteSpace(DpsEndpoint) + && !string.IsNullOrWhiteSpace(DpsIdScope) + && !string.IsNullOrWhiteSpace(DeviceId) + && !string.IsNullOrWhiteSpace(DeviceSymmetricKey), + "connectionstring" => !string.IsNullOrWhiteSpace(PrimaryConnectionString), + _ => throw new ArgumentException($"Unrecognized value for device provisioning received: {DeviceSecurityType}." + + $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."), + }; + } + } +} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs b/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs new file mode 100644 index 0000000000..f31383627b --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using CommandLine; +using Microsoft.Azure.Devices.Provisioning.Client; +using Microsoft.Azure.Devices.Provisioning.Client.PlugAndPlay; +using Microsoft.Azure.Devices.Provisioning.Client.Transport; +using Microsoft.Azure.Devices.Shared; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + public class Program + { + // DTDL interface used: https://github.com/Azure/iot-plugandplay-models/blob/main/dtmi/com/example/temperaturecontroller-2.json + // The TemperatureController model contains 2 Thermostat components that implement different versions of Thermostat models. + // Both Thermostat models are identical in definition but this is done to allow IoT Central to handle + // TemperatureController model correctly. + private const string ModelId = "dtmi:com:example:TemperatureController;2"; + + private static ILogger s_logger; + + public static async Task Main(string[] args) + { + // Parse application parameters + Parameters parameters = null; + ParserResult result = Parser.Default.ParseArguments(args) + .WithParsed(parsedParams => + { + parameters = parsedParams; + }) + .WithNotParsed(errors => + { + Environment.Exit(1); + }); + + s_logger = InitializeConsoleDebugLogger(); + if (!parameters.Validate(s_logger)) + { + throw new ArgumentException("Required parameters are not set. Please recheck required variables by using \"--help\""); + } + + var runningTime = parameters.ApplicationRunningTime != null + ? TimeSpan.FromSeconds((double)parameters.ApplicationRunningTime) + : Timeout.InfiniteTimeSpan; + + s_logger.LogInformation("Press Control+C to quit the sample."); + using var cts = new CancellationTokenSource(runningTime); + Console.CancelKeyPress += (sender, eventArgs) => + { + eventArgs.Cancel = true; + cts.Cancel(); + s_logger.LogInformation("Sample execution cancellation requested; will exit."); + }; + + s_logger.LogDebug($"Set up the device client."); + using DeviceClient deviceClient = await SetupDeviceClientAsync(parameters, cts.Token); + var sample = new TemperatureControllerSample(deviceClient, s_logger); + await sample.PerformOperationsAsync(cts.Token); + + // PerformOperationsAsync is designed to run until cancellation has been explicitly requested, either through + // cancellation token expiration or by Console.CancelKeyPress. + // As a result, by the time the control reaches the call to close the device client, the cancellation token source would + // have already had cancellation requested. + // Hence, if you want to pass a cancellation token to any subsequent calls, a new token needs to be generated. + // For device client APIs, you can also call them without a cancellation token, which will set a default + // cancellation timeout of 4 minutes: https://github.com/Azure/azure-iot-sdk-csharp/blob/64f6e9f24371bc40ab3ec7a8b8accbfb537f0fe1/iothub/device/src/InternalClient.cs#L1922 + await deviceClient.CloseAsync(); + } + + private static ILogger InitializeConsoleDebugLogger() + { + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddFilter(level => level >= LogLevel.Debug) + .AddConsole(options => + { + options.TimestampFormat = "[MM/dd/yyyy HH:mm:ss]"; + }); + }); + + return loggerFactory.CreateLogger(); + } + + private static async Task SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken) + { + DeviceClient deviceClient; + switch (parameters.DeviceSecurityType.ToLowerInvariant()) + { + case "dps": + s_logger.LogDebug($"Initializing via DPS"); + DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken); + var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey); + deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod); + break; + + case "connectionstring": + s_logger.LogDebug($"Initializing via IoT Hub connection string"); + deviceClient = InitializeDeviceClient(parameters.PrimaryConnectionString); + break; + + default: + throw new ArgumentException($"Unrecognized value for device provisioning received: {parameters.DeviceSecurityType}." + + $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."); + } + return deviceClient; + } + + // Provision a device via DPS, by sending the PnP model Id as DPS payload. + private static async Task ProvisionDeviceAsync(Parameters parameters, CancellationToken cancellationToken) + { + SecurityProvider symmetricKeyProvider = new SecurityProviderSymmetricKey(parameters.DeviceId, parameters.DeviceSymmetricKey, null); + ProvisioningTransportHandler mqttTransportHandler = new ProvisioningTransportHandlerMqtt(); + ProvisioningDeviceClient pdc = ProvisioningDeviceClient.Create(parameters.DpsEndpoint, parameters.DpsIdScope, symmetricKeyProvider, mqttTransportHandler); + + var pnpPayload = new ProvisioningRegistrationAdditionalData + { + JsonData = PnpConvention.CreateDpsPayload(ModelId), + }; + return await pdc.RegisterAsync(pnpPayload, cancellationToken); + } + + // Initialize the device client instance using connection string based authentication, over Mqtt protocol (TCP, with fallback over Websocket) and + // setting the ModelId into ClientOptions.This method also sets a connection status change callback, that will get triggered any time the device's + // connection status changes. + private static DeviceClient InitializeDeviceClient(string deviceConnectionString) + { + var options = new ClientOptions + { + ModelId = ModelId, + + // Specify a custom System.Text.Json based PayloadConvention to be used. + PayloadConvention = SystemTextJsonPayloadConvention.Instance, + }; + + DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); + deviceClient.SetConnectionStatusChangesHandler((status, reason) => + { + s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); + }); + + return deviceClient; + } + + // Initialize the device client instance using symmetric key based authentication, over Mqtt protocol (TCP, with fallback over Websocket) + // and setting the ModelId into ClientOptions. This method also sets a connection status change callback, that will get triggered any time the device's connection status changes. + private static DeviceClient InitializeDeviceClient(string hostname, IAuthenticationMethod authenticationMethod) + { + var options = new ClientOptions + { + ModelId = ModelId, + }; + + DeviceClient deviceClient = DeviceClient.Create(hostname, authenticationMethod, TransportType.Mqtt, options); + deviceClient.SetConnectionStatusChangesHandler((status, reason) => + { + s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); + }); + + return deviceClient; + } + } +} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json b/iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json new file mode 100644 index 0000000000..6ae8d6e736 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json @@ -0,0 +1,22 @@ +{ + "profiles": { + "Hub": { + "commandName": "Project", + "environmentVariables": { + "IOTHUB_DEVICE_SECURITY_TYPE": "connectionString", + "IOTHUB_DEVICE_CONNECTION_STRING": "" + } + }, + "DPS": { + "commandName": "Project", + "environmentVariables": { + "IOTHUB_DEVICE_SECURITY_TYPE": "dps", + "IOTHUB_DEVICE_DPS_ID_SCOPE": "", + "IOTHUB_DEVICE_DPS_DEVICE_ID": "", + "IOTHUB_DEVICE_DPS_DEVICE_KEY": "", + "IOTHUB_DEVICE_DPS_ENDPOINT": "global.azure-devices-provisioning.net" + }, + "sqlDebugging": false + } + } +} \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs new file mode 100644 index 0000000000..02cced0002 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + /// + /// A that uses . + /// + public class SystemTextJsonPayloadConvention : PayloadConvention + { + public static readonly SystemTextJsonPayloadConvention Instance = new SystemTextJsonPayloadConvention(); + + public override PayloadSerializer PayloadSerializer { get; } = SystemTextJsonPayloadSerializer.Instance; + + public override PayloadEncoder PayloadEncoder { get; } = Utf8PayloadEncoder.Instance; + } +} diff --git a/shared/src/SystemTextJsonPayloadSerializer.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs similarity index 93% rename from shared/src/SystemTextJsonPayloadSerializer.cs rename to iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs index 31602cbc6e..c3cad86935 100644 --- a/shared/src/SystemTextJsonPayloadSerializer.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#if !NET451 - using System.Text.Json; +using Microsoft.Azure.Devices.Shared; -namespace Microsoft.Azure.Devices.Shared +namespace Microsoft.Azure.Devices.Client.Samples { /// /// A implementation. @@ -40,7 +39,7 @@ public override T DeserializeToType(string stringToDeserialize) /// public override T ConvertFromObject(object objectToConvert) { - return DeserializeToType(((JsonElement)objectToConvert).ToString()); + return DeserializeToType(SerializeToString(objectToConvert)); } /// @@ -66,5 +65,3 @@ public override IWritablePropertyResponse CreateWritablePropertyResponse(object } } } - -#endif diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj new file mode 100644 index 0000000000..d0fbcbc34a --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp3.1 + $(MSBuildProjectDirectory)\..\..\..\..\.. + + + + + + + + + + + + + diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs new file mode 100644 index 0000000000..fad873354b --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs @@ -0,0 +1,407 @@ +// 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.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + public class TemperatureControllerSample + { + private const string Thermostat1 = "thermostat1"; + private const string Thermostat2 = "thermostat2"; + + private static readonly Random s_random = new Random(); + private static readonly TimeSpan s_sleepDuration = TimeSpan.FromSeconds(5); + + private readonly DeviceClient _deviceClient; + private readonly ILogger _logger; + + // Dictionary to hold the temperature updates sent over each "Thermostat" component. + // NOTE: Memory constrained devices should leverage storage capabilities of an external service to store this + // information and perform computation. + // See https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services for more details. + private readonly Dictionary> _temperatureReadingsDateTimeOffset = + new Dictionary>(); + + // Dictionary to hold the current temperature for each "Thermostat" component. + private readonly Dictionary _temperature = new Dictionary(); + + // Dictionary to hold the max temperature since last reboot, for each "Thermostat" component. + private readonly Dictionary _maxTemp = new Dictionary(); + + public TemperatureControllerSample(DeviceClient deviceClient, ILogger logger) + { + _deviceClient = deviceClient ?? throw new ArgumentNullException($"{nameof(deviceClient)} cannot be null."); + _logger = logger ?? LoggerFactory.Create(builer => builer.AddConsole()).CreateLogger(); + } + + public async Task PerformOperationsAsync(CancellationToken cancellationToken) + { + // Set handler to receive and respond to writable property update requests. + _logger.LogDebug("Subscribe to writable property updates."); + await _deviceClient.SubscribeToWritablePropertiesEventAsync(HandlePropertyUpdatesAsync, null, cancellationToken); + + // Set handler to receive and respond to commands. + _logger.LogDebug($"Subscribe to commands."); + await _deviceClient.SubscribeToCommandsAsync(HandleCommandsAsync, null, cancellationToken); + + // Report device information on "deviceInformation" component. + // This is a component-level property update call. + await UpdateDeviceInformationPropertyAsync(cancellationToken); + + // Verify if the device has previously reported the current value for property "serialNumber". + // If the expected value has not been previously reported then send device serial number over property update. + // This is a root-level property update call. + await SendDeviceSerialNumberPropertyIfNotCurrentAsync(cancellationToken); + + bool temperatureReset = true; + _maxTemp[Thermostat1] = 0d; + _maxTemp[Thermostat2] = 0d; + + // Periodically send "temperature" over telemetry - on "Thermostat" components. + // Send "maxTempSinceLastReboot" over property update, when a new max temperature is reached - on "Thermostat" components. + while (!cancellationToken.IsCancellationRequested) + { + if (temperatureReset) + { + // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component. + _temperature[Thermostat1] = GenerateTemperatureWithinRange(45, 5); + _temperature[Thermostat2] = GenerateTemperatureWithinRange(45, 5); + } + + // Send temperature updates over telemetry and the value of max temperature since last reboot over property update. + // Both of these are component-level calls. + await SendTemperatureAsync(Thermostat1, cancellationToken); + await SendTemperatureAsync(Thermostat2, cancellationToken); + + // Send working set of device memory over telemetry. + // This is a root-level telemetry call. + await SendDeviceMemoryTelemetryAsync(cancellationToken); + + temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0; + await Task.Delay(s_sleepDuration); + } + } + + // The callback to handle property update requests. + private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableProperties, object userContext) + { + foreach (KeyValuePair writableProperty in writableProperties) + { + // The dispatcher key will be either a root-level property name or a component name. + switch (writableProperty.Key) + { + case Thermostat1: + case Thermostat2: + const string targetTemperatureProperty = "targetTemperature"; + if (writableProperties.TryGetValue(writableProperty.Key, targetTemperatureProperty, out double targetTemperatureRequested)) + { + await HandleTargetTemperatureUpdateRequestAsync(writableProperty.Key, targetTemperatureRequested, writableProperties.Version, userContext); + break; + } + else + { + _logger.LogWarning($"Property: Received an unrecognized property update from service for component {writableProperty.Key}:" + + $"\n[ {writableProperty.Value} ]."); + break; + } + + default: + _logger.LogWarning($"Property: Received an unrecognized property update from service:" + + $"\n[ {writableProperty.Key}: {writableProperty.Value} ]."); + break; + } + } + } + + // The callback to handle target temperature property update requests for a component. + private async Task HandleTargetTemperatureUpdateRequestAsync(string componentName, double targetTemperature, long version, object userContext) + { + const string targetTemperatureProperty = "targetTemperature"; + _logger.LogDebug($"Property: Received - component=\"{componentName}\", [ \"{targetTemperatureProperty}\": {targetTemperature}°C ]."); + + _temperature[componentName] = targetTemperature; + var reportedProperty = new ClientPropertyCollection(); + reportedProperty.Add(componentName, targetTemperatureProperty, _temperature[componentName], StatusCodes.Accepted, version, "Successfully updated target temperature."); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); + + _logger.LogDebug($"Property: Update - component=\"{componentName}\", {reportedProperty.GetSerializedString()} is {nameof(StatusCodes.OK)} " + + $"with a version of {updateResponse.Version}."); + } + + // The callback to handle command invocation requests. + private Task HandleCommandsAsync(CommandRequest commandRequest, object userContext) + { + // In this approach, we'll first switch through the component name returned and handle each component-level command. + // For the "default" case, we'll first check if the component name is null. + // If null, then this would be a root-level command request, so we'll switch through each root-level command. + // If not null, then this is a component-level command that has not been implemented. + + // Switch through CommandRequest.ComponentName to handle all component-level commands. + switch (commandRequest.ComponentName) + { + case Thermostat1: + case Thermostat2: + // For each component, switch through CommandRequest.CommandName to handle the specific component-level command. + switch (commandRequest.CommandName) + { + case "getMaxMinReport": + return HandleMaxMinReportCommandAsync(commandRequest, userContext); + + default: + _logger.LogWarning($"Received a command request that isn't" + + $" implemented - component name = {commandRequest.ComponentName}, command name = {commandRequest.CommandName}"); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + + // For the default case, first check if CommandRequest.ComponentName is null. + default: + // If CommandRequest.ComponentName is null, then this is a root-level command request. + if (commandRequest.ComponentName == null) + { + // Switch through CommandRequest.CommandName to handle all root-level commands. + switch (commandRequest.CommandName) + { + case "reboot": + return HandleRebootCommandAsync(commandRequest, userContext); + + default: + _logger.LogWarning($"Received a command request that isn't" + + $" implemented - command name = {commandRequest.CommandName}"); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + } + else + { + _logger.LogWarning($"Received a command request that isn't" + + $" implemented - component name = {commandRequest.ComponentName}, command name = {commandRequest.CommandName}"); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + } + } + + // The callback to handle root-level "reboot" command. + // This method will send a temperature update (of 0°C) over telemetry for both associated components. + private async Task HandleRebootCommandAsync(CommandRequest commandRequest, object userContext) + { + try + { + int delay = commandRequest.GetData(); + + _logger.LogDebug($"Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {delay} seconds)."); + await Task.Delay(delay * 1000); + + _temperature[Thermostat1] = _maxTemp[Thermostat1] = 0; + _temperature[Thermostat2] = _maxTemp[Thermostat2] = 0; + + _temperatureReadingsDateTimeOffset.Clear(); + _logger.LogDebug($"Command: Reboot completed."); + + return new CommandResponse(StatusCodes.OK); + } + catch (JsonReaderException ex) + { + _logger.LogDebug($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); + return new CommandResponse(StatusCodes.BadRequest); + } + } + + // The callback to handle component-level "getMaxMinReport" command. + // This method will returns the max, min and average temperature from the specified time to the current time. + private Task HandleMaxMinReportCommandAsync(CommandRequest commandRequest, object userContext) + { + try + { + DateTimeOffset sinceInUtc = commandRequest.GetData(); + _logger.LogDebug($"Command: Received - Generating max, min and avg temperature report since " + + $"{sinceInUtc.LocalDateTime}."); + + if (_temperatureReadingsDateTimeOffset.ContainsKey(commandRequest.ComponentName)) + { + Dictionary allReadings = _temperatureReadingsDateTimeOffset[commandRequest.ComponentName]; + Dictionary filteredReadings = allReadings.Where(i => i.Key > sinceInUtc) + .ToDictionary(i => i.Key, i => i.Value); + + if (filteredReadings != null && filteredReadings.Any()) + { + var report = new TemperatureReport + { + MaximumTemperature = filteredReadings.Values.Max(), + MinimumTemperature = filteredReadings.Values.Min(), + AverageTemperature = filteredReadings.Values.Average(), + StartTime = filteredReadings.Keys.Min(), + EndTime = filteredReadings.Keys.Max(), + }; + + _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\", MaxMinReport since {sinceInUtc.LocalDateTime}:" + + $" maxTemp={report.MaximumTemperature}, minTemp={report.MinimumTemperature}, avgTemp={report.AverageTemperature}, " + + $"startTime={report.StartTime.LocalDateTime}, endTime={report.EndTime.LocalDateTime}"); + + return Task.FromResult(new CommandResponse(report, StatusCodes.OK)); + } + + _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\"," + + $" no relevant readings found since {sinceInUtc.LocalDateTime}, cannot generate any report."); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + + _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\", no temperature readings sent yet," + + $" cannot generate any report."); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + catch (JsonReaderException ex) + { + _logger.LogError($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); + + return Task.FromResult(new CommandResponse(StatusCodes.BadRequest)); + } + } + + // Report the property values on "deviceInformation" component. + // This is a component-level property update call. + private async Task UpdateDeviceInformationPropertyAsync(CancellationToken cancellationToken) + { + const string componentName = "deviceInformation"; + var deviceInformationProperties = new Dictionary + { + { "manufacturer", "element15" }, + { "model", "ModelIDxcdvmk" }, + { "swVersion", "1.0.0" }, + { "osName", "Windows 10" }, + { "processorArchitecture", "64-bit" }, + { "processorManufacturer", "Intel" }, + { "totalStorage", 256 }, + { "totalMemory", 1024 }, + }; + var deviceInformation = new ClientPropertyCollection(); + deviceInformation.Add(componentName, deviceInformationProperties); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(deviceInformation, cancellationToken); + + _logger.LogDebug($"Property: Update - component = '{componentName}', properties update is complete " + + $"with a version of {updateResponse.Version}."); + } + + // Send working set of device memory over telemetry. + // This is a root-level telemetry call. + private async Task SendDeviceMemoryTelemetryAsync(CancellationToken cancellationToken) + { + const string workingSetName = "workingSet"; + long workingSet = Process.GetCurrentProcess().PrivateMemorySize64 / 1024; + using var telemetryMessage = new TelemetryMessage + { + Telemetry = { [workingSetName] = workingSet }, + }; + + await _deviceClient.SendTelemetryAsync(telemetryMessage, cancellationToken); + + _logger.LogDebug($"Telemetry: Sent - {telemetryMessage.Telemetry.GetSerializedString()} in KB."); + } + + // Verify if the device has previously reported the current value for property "serialNumber". + // If the expected value has not been previously reported then send device serial number over property update. + // This is a root-level property update call. + private async Task SendDeviceSerialNumberPropertyIfNotCurrentAsync(CancellationToken cancellationToken) + { + const string serialNumber = "serialNumber"; + const string currentSerialNumber = "SR-123456"; + + // Verify if the device has previously reported the current value for property "serialNumber". + // If the expected value has not been previously reported then report it. + + // Retrieve the device's properties. + ClientProperties properties = await _deviceClient.GetClientPropertiesAsync(cancellationToken); + + if (!properties.TryGetValue(serialNumber, out string serialNumberReported) + || serialNumberReported != currentSerialNumber) + { + var reportedProperties = new ClientPropertyCollection(); + reportedProperties.Add(serialNumber, currentSerialNumber); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties, cancellationToken); + + _logger.LogDebug($"Property: Update - {reportedProperties.GetSerializedString()} is complete " + + $"with a version of {updateResponse.Version}."); + } + } + + // Send temperature updates over telemetry. + // This also sends the value of max temperature since last reboot over property update. + private async Task SendTemperatureAsync(string componentName, CancellationToken cancellationToken) + { + await SendTemperatureTelemetryAsync(componentName, cancellationToken); + + double maxTemp = _temperatureReadingsDateTimeOffset[componentName].Values.Max(); + if (maxTemp > _maxTemp[componentName]) + { + _maxTemp[componentName] = maxTemp; + await UpdateMaxTemperatureSinceLastRebootAsync(componentName, cancellationToken); + } + } + + // Send temperature update over telemetry. + // This is a component-level telemetry call. + private async Task SendTemperatureTelemetryAsync(string componentName, CancellationToken cancellationToken) + { + const string telemetryName = "temperature"; + double currentTemperature = _temperature[componentName]; + + using var telemtryMessage = new TelemetryMessage(componentName) + { + Telemetry = { [telemetryName] = currentTemperature }, + }; + + await _deviceClient.SendTelemetryAsync(telemtryMessage, cancellationToken); + + _logger.LogDebug($"Telemetry: Sent - component=\"{componentName}\", {telemtryMessage.Telemetry.GetSerializedString()} in °C."); + + if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName)) + { + _temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow, currentTemperature); + } + else + { + _temperatureReadingsDateTimeOffset.TryAdd( + componentName, + new Dictionary + { + { DateTimeOffset.UtcNow, currentTemperature }, + }); + } + } + + // Send temperature over reported property update. + // This is a component-level property update. + private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName, CancellationToken cancellationToken) + { + const string propertyName = "maxTempSinceLastReboot"; + double maxTemp = _maxTemp[componentName]; + var reportedProperties = new ClientPropertyCollection(); + reportedProperties.Add(componentName, propertyName, maxTemp); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties, cancellationToken); + + _logger.LogDebug($"Property: Update - component=\"{componentName}\", {reportedProperties.GetSerializedString()}" + + $" in °C is complete with a version of {updateResponse.Version}."); + } + + private static double GenerateTemperatureWithinRange(int max = 50, int min = 0) + { + return Math.Round(s_random.NextDouble() * (max - min) + min, 1); + } + } +} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs new file mode 100644 index 0000000000..f7b25e9ec7 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs @@ -0,0 +1,26 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + public class TemperatureReport + { + [JsonPropertyName("maxTemp")] + public double MaximumTemperature { get; set; } + + [JsonPropertyName("minTemp")] + public double MinimumTemperature { get; set; } + + [JsonPropertyName("avgTemp")] + public double AverageTemperature { get; set; } + + [JsonPropertyName("startTime")] + public DateTimeOffset StartTime { get; set; } + + [JsonPropertyName("endTime")] + public DateTimeOffset EndTime { get; set; } + } +} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json b/iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json new file mode 100644 index 0000000000..46b21f85cb --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json @@ -0,0 +1,89 @@ +{ + "@context": "dtmi:dtdl:context;2", + "@id": "dtmi:com:example:Thermostat;1", + "@type": "Interface", + "displayName": "Thermostat", + "description": "Reports current temperature and provides desired temperature control.", + "contents": [ + { + "@type": [ + "Telemetry", + "Temperature" + ], + "name": "temperature", + "displayName" : "Temperature", + "description" : "Temperature in degrees Celsius.", + "schema": "double", + "unit": "degreeCelsius" + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "targetTemperature", + "schema": "double", + "displayName": "Target Temperature", + "description": "Allows to remotely specify the desired target temperature.", + "unit" : "degreeCelsius", + "writable": true + }, + { + "@type": [ + "Property", + "Temperature" + ], + "name": "maxTempSinceLastReboot", + "schema": "double", + "unit" : "degreeCelsius", + "displayName": "Max temperature since last reboot.", + "description": "Returns the max temperature since last device reboot." + }, + { + "@type": "Command", + "name": "getMaxMinReport", + "displayName": "Get Max-Min report.", + "description": "This command returns the max, min and average temperature from the specified time to the current time.", + "request": { + "name": "since", + "displayName": "Since", + "description": "Period to return the max-min report.", + "schema": "dateTime" + }, + "response": { + "name" : "tempReport", + "displayName": "Temperature Report", + "schema": { + "@type": "Object", + "fields": [ + { + "name": "maxTemp", + "displayName": "Max temperature", + "schema": "double" + }, + { + "name": "minTemp", + "displayName": "Min temperature", + "schema": "double" + }, + { + "name" : "avgTemp", + "displayName": "Average Temperature", + "schema": "double" + }, + { + "name" : "startTime", + "displayName": "Start Time", + "schema": "dateTime" + }, + { + "name" : "endTime", + "displayName": "End Time", + "schema": "dateTime" + } + ] + } + } + } + ] +} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs b/iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs new file mode 100644 index 0000000000..9f8f5b99ff --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using CommandLine; +using Microsoft.Extensions.Logging; +using System; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + /// + /// Parameters for the application supplied via command line arguments. + /// If the parameter is not supplied via command line args, it will look for it in environment variables. + /// + internal class Parameters + { + [Option( + "DeviceSecurityType", + HelpText = "(Required) The flow that will be used for connecting the device for the sample. Possible case-insensitive values include: dps, connectionString." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\".")] + public string DeviceSecurityType { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_SECURITY_TYPE"); + + [Option( + 'p', + "PrimaryConnectionString", + HelpText = "(Required if DeviceSecurityType is \"connectionString\"). \nThe primary connection string for the device to simulate." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_CONNECTION_STRING\".")] + public string PrimaryConnectionString { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_CONNECTION_STRING"); + + [Option( + 'e', + "DpsEndpoint", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS endpoint to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ENDPOINT\".")] + public string DpsEndpoint { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ENDPOINT"); + + [Option( + 'i', + "DpsIdScope", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS ID Scope to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ID_SCOPE\".")] + public string DpsIdScope { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ID_SCOPE"); + + [Option( + 'd', + "DeviceId", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device registration Id to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_ID\".")] + public string DeviceId { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_ID"); + + [Option( + 'k', + "DeviceSymmetricKey", + HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device symmetric key to use during device provisioning." + + "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_KEY\".")] + public string DeviceSymmetricKey { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_KEY"); + + [Option( + 'r', + "Application running time (in seconds)", + Required = false, + HelpText = "The running time for this console application. Leave it unassigned to run the application until it is explicitly canceled using Control+C.")] + public double? ApplicationRunningTime { get; set; } + + public bool Validate(ILogger logger) + { + if (string.IsNullOrWhiteSpace(DeviceSecurityType)) + { + logger.LogWarning("Device provisioning type not set, please set the environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\"" + + "or pass in \"-s | --DeviceSecurityType\" through command line. \nWill default to using \"dps\" flow."); + + DeviceSecurityType = "dps"; + } + + return (DeviceSecurityType.ToLowerInvariant()) switch + { + "dps" => !string.IsNullOrWhiteSpace(DpsEndpoint) + && !string.IsNullOrWhiteSpace(DpsIdScope) + && !string.IsNullOrWhiteSpace(DeviceId) + && !string.IsNullOrWhiteSpace(DeviceSymmetricKey), + "connectionstring" => !string.IsNullOrWhiteSpace(PrimaryConnectionString), + _ => throw new ArgumentException($"Unrecognized value for device provisioning received: {DeviceSecurityType}." + + $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."), + }; + } + } +} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Program.cs b/iothub/device/samples/convention-based-samples/Thermostat/Program.cs new file mode 100644 index 0000000000..69dff54071 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/Program.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using CommandLine; +using Microsoft.Azure.Devices.Provisioning.Client; +using Microsoft.Azure.Devices.Provisioning.Client.Transport; +using Microsoft.Azure.Devices.Shared; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + public class Program + { + // DTDL interface used: https://github.com/Azure/iot-plugandplay-models/blob/main/dtmi/com/example/thermostat-1.json + private const string ModelId = "dtmi:com:example:Thermostat;1"; + + private static ILogger s_logger; + + public static async Task Main(string[] args) + { + // Parse application parameters + Parameters parameters = null; + ParserResult result = Parser.Default.ParseArguments(args) + .WithParsed(parsedParams => + { + parameters = parsedParams; + }) + .WithNotParsed(errors => + { + Environment.Exit(1); + }); + + s_logger = InitializeConsoleDebugLogger(); + if (!parameters.Validate(s_logger)) + { + throw new ArgumentException("Required parameters are not set. Please recheck required variables by using \"--help\""); + } + + var runningTime = parameters.ApplicationRunningTime != null + ? TimeSpan.FromSeconds((double)parameters.ApplicationRunningTime) + : Timeout.InfiniteTimeSpan; + + s_logger.LogInformation("Press Control+C to quit the sample."); + using var cts = new CancellationTokenSource(runningTime); + Console.CancelKeyPress += (sender, eventArgs) => + { + eventArgs.Cancel = true; + cts.Cancel(); + s_logger.LogInformation("Sample execution cancellation requested; will exit."); + }; + + s_logger.LogDebug($"Set up the device client."); + using DeviceClient deviceClient = await SetupDeviceClientAsync(parameters, cts.Token); + var sample = new ThermostatSample(deviceClient, s_logger); + await sample.PerformOperationsAsync(cts.Token); + + // PerformOperationsAsync is designed to run until cancellation has been explicitly requested, either through + // cancellation token expiration or by Console.CancelKeyPress. + // As a result, by the time the control reaches the call to close the device client, the cancellation token source would + // have already had cancellation requested. + // Hence, if you want to pass a cancellation token to any subsequent calls, a new token needs to be generated. + // For device client APIs, you can also call them without a cancellation token, which will set a default + // cancellation timeout of 4 minutes: https://github.com/Azure/azure-iot-sdk-csharp/blob/64f6e9f24371bc40ab3ec7a8b8accbfb537f0fe1/iothub/device/src/InternalClient.cs#L1922 + await deviceClient.CloseAsync(); + } + + private static ILogger InitializeConsoleDebugLogger() + { + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddFilter(level => level >= LogLevel.Debug) + .AddConsole(options => + { + options.TimestampFormat = "[MM/dd/yyyy HH:mm:ss]"; + }); + }); + + return loggerFactory.CreateLogger(); + } + + private static async Task SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken) + { + DeviceClient deviceClient; + switch (parameters.DeviceSecurityType.ToLowerInvariant()) + { + case "dps": + s_logger.LogDebug($"Initializing via DPS"); + DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken); + var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey); + deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod); + break; + + case "connectionstring": + s_logger.LogDebug($"Initializing via IoT Hub connection string"); + deviceClient = InitializeDeviceClient(parameters.PrimaryConnectionString); + break; + + default: + throw new ArgumentException($"Unrecognized value for device provisioning received: {parameters.DeviceSecurityType}." + + $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."); + } + + return deviceClient; + } + + // Provision a device via DPS, by sending the PnP model Id as DPS payload. + private static async Task ProvisionDeviceAsync(Parameters parameters, CancellationToken cancellationToken) + { + SecurityProvider symmetricKeyProvider = new SecurityProviderSymmetricKey(parameters.DeviceId, parameters.DeviceSymmetricKey, null); + ProvisioningTransportHandler mqttTransportHandler = new ProvisioningTransportHandlerMqtt(); + ProvisioningDeviceClient pdc = ProvisioningDeviceClient.Create(parameters.DpsEndpoint, parameters.DpsIdScope, + symmetricKeyProvider, mqttTransportHandler); + + var pnpPayload = new ProvisioningRegistrationAdditionalData + { + JsonData = $"{{ \"modelId\": \"{ModelId}\" }}", + }; + return await pdc.RegisterAsync(pnpPayload, cancellationToken); + } + + // Initialize the device client instance using connection string based authentication, over Mqtt protocol (TCP, with fallback over Websocket) + // and setting the ModelId into ClientOptions. + // This method also sets a connection status change callback, that will get triggered any time the device's connection status changes. + private static DeviceClient InitializeDeviceClient(string deviceConnectionString) + { + var options = new ClientOptions + { + ModelId = ModelId, + }; + + DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); + deviceClient.SetConnectionStatusChangesHandler((status, reason) => + { + s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); + }); + + return deviceClient; + } + + // Initialize the device client instance using symmetric key based authentication, over Mqtt protocol (TCP, with fallback over Websocket) and setting the ModelId into ClientOptions. + // This method also sets a connection status change callback, that will get triggered any time the device's connection status changes. + private static DeviceClient InitializeDeviceClient(string hostname, IAuthenticationMethod authenticationMethod) + { + var options = new ClientOptions + { + ModelId = ModelId, + }; + + DeviceClient deviceClient = DeviceClient.Create(hostname, authenticationMethod, TransportType.Mqtt, options); + deviceClient.SetConnectionStatusChangesHandler((status, reason) => + { + s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); + }); + + return deviceClient; + } + } +} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json b/iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json new file mode 100644 index 0000000000..6ae8d6e736 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json @@ -0,0 +1,22 @@ +{ + "profiles": { + "Hub": { + "commandName": "Project", + "environmentVariables": { + "IOTHUB_DEVICE_SECURITY_TYPE": "connectionString", + "IOTHUB_DEVICE_CONNECTION_STRING": "" + } + }, + "DPS": { + "commandName": "Project", + "environmentVariables": { + "IOTHUB_DEVICE_SECURITY_TYPE": "dps", + "IOTHUB_DEVICE_DPS_ID_SCOPE": "", + "IOTHUB_DEVICE_DPS_DEVICE_ID": "", + "IOTHUB_DEVICE_DPS_DEVICE_KEY": "", + "IOTHUB_DEVICE_DPS_ENDPOINT": "global.azure-devices-provisioning.net" + }, + "sqlDebugging": false + } + } +} \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs b/iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs new file mode 100644 index 0000000000..44996a7961 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs @@ -0,0 +1,26 @@ +// 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 Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + public class TemperatureReport + { + [JsonProperty("maxTemp")] + public double MaximumTemperature { get; set; } + + [JsonProperty("minTemp")] + public double MinimumTemperature { get; set; } + + [JsonProperty("avgTemp")] + public double AverageTemperature { get; set; } + + [JsonProperty("startTime")] + public DateTimeOffset StartTime { get; set; } + + [JsonProperty("endTime")] + public DateTimeOffset EndTime { get; set; } + } +} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj b/iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj new file mode 100644 index 0000000000..d0fbcbc34a --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp3.1 + $(MSBuildProjectDirectory)\..\..\..\..\.. + + + + + + + + + + + + + diff --git a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs new file mode 100644 index 0000000000..73d6e0c072 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.Client.Samples +{ + public class ThermostatSample + { + private static readonly Random s_random = new Random(); + private static readonly TimeSpan s_sleepDuration = TimeSpan.FromSeconds(5); + + private double _temperature = 0d; + private double _maxTemp = 0d; + + // Dictionary to hold the temperature updates sent over. + // NOTE: Memory constrained devices should leverage storage capabilities of an external service to store this information and perform computation. + // See https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services for more details. + private readonly Dictionary _temperatureReadingsDateTimeOffset = new Dictionary(); + + private readonly DeviceClient _deviceClient; + private readonly ILogger _logger; + + public ThermostatSample(DeviceClient deviceClient, ILogger logger) + { + _deviceClient = deviceClient ?? throw new ArgumentNullException($"{nameof(deviceClient)} cannot be null."); + _logger = logger ?? LoggerFactory.Create(builer => builer.AddConsole()).CreateLogger(); + } + + public async Task PerformOperationsAsync(CancellationToken cancellationToken) + { + // Set handler to receive and respond to writable property update requests. + _logger.LogDebug($"Subscribe to writable property updates."); + await _deviceClient.SubscribeToWritablePropertiesEventAsync(HandlePropertyUpdatesAsync, null, cancellationToken); + + // Set handler to receive and respond to commands. + _logger.LogDebug($"Subscribe to commands."); + await _deviceClient.SubscribeToCommandsAsync(HandleCommandsAsync, null, cancellationToken); + + bool temperatureReset = true; + + // Periodically send "temperature" over telemetry. + // Send "maxTempSinceLastReboot" over property update, when a new max temperature is reached. + while (!cancellationToken.IsCancellationRequested) + { + if (temperatureReset) + { + // Generate a random value between 5.0°C and 45.0°C for the current temperature reading. + _temperature = GenerateTemperatureWithinRange(45, 5); + temperatureReset = false; + } + + // Send temperature updates over telemetry and the value of max temperature since last reboot over property update. + await SendTemperatureAsync(); + + await Task.Delay(s_sleepDuration); + } + } + + // The callback to handle property update requests. + private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableProperties, object userContext) + { + foreach (KeyValuePair writableProperty in writableProperties) + { + switch (writableProperty.Key) + { + case "targetTemperature": + const string tagetTemperatureProperty = "targetTemperature"; + double targetTemperatureRequested = Convert.ToDouble(writableProperty.Value); + _logger.LogDebug($"Property: Received - [ \"{tagetTemperatureProperty}\": {targetTemperatureRequested}°C ]."); + + _temperature = targetTemperatureRequested; + var reportedProperty = new ClientPropertyCollection(); + reportedProperty.Add(tagetTemperatureProperty, _temperature, StatusCodes.OK, writableProperties.Version, "Successfully updated target temperature"); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); + + _logger.LogDebug($"Property: Update - {reportedProperty.GetSerializedString()} is {nameof(StatusCodes.OK)} " + + $"with a version of {updateResponse.Version}."); + + break; + + default: + _logger.LogWarning($"Property: Received an unrecognized property update from service:\n[ {writableProperty.Key}: {writableProperty.Value} ]."); + break; + } + } + } + + // The callback to handle command invocation requests. + private Task HandleCommandsAsync(CommandRequest commandRequest, object userContext) + { + // In this approach, we'll switch through the command name returned and handle each root-level command. + switch (commandRequest.CommandName) + { + case "getMaxMinReport": + try + { + DateTimeOffset sinceInUtc = commandRequest.GetData(); + _logger.LogDebug($"Command: Received - Generating max, min and avg temperature report since " + + $"{sinceInUtc.LocalDateTime}."); + + Dictionary filteredReadings = _temperatureReadingsDateTimeOffset + .Where(i => i.Key > sinceInUtc) + .ToDictionary(i => i.Key, i => i.Value); + + if (filteredReadings != null && filteredReadings.Any()) + { + var report = new TemperatureReport + { + MaximumTemperature = filteredReadings.Values.Max(), + MinimumTemperature = filteredReadings.Values.Min(), + AverageTemperature = filteredReadings.Values.Average(), + StartTime = filteredReadings.Keys.Min(), + EndTime = filteredReadings.Keys.Max(), + }; + + _logger.LogDebug($"Command: MaxMinReport since {sinceInUtc.LocalDateTime}:" + + $" maxTemp={report.MaximumTemperature}, minTemp={report.MinimumTemperature}, avgTemp={report.AverageTemperature}, " + + $"startTime={report.StartTime.LocalDateTime}, endTime={report.EndTime.LocalDateTime}"); + + return Task.FromResult(new CommandResponse(report, StatusCodes.OK)); + } + + _logger.LogDebug($"Command: No relevant readings found since {sinceInUtc.LocalDateTime}, cannot generate any report."); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + catch (JsonReaderException ex) + { + _logger.LogError($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); + + return Task.FromResult(new CommandResponse(StatusCodes.BadRequest)); + } + + default: + _logger.LogWarning($"Received a command request that isn't" + + $" implemented - command name = {commandRequest.CommandName}"); + + return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + } + } + + // Send temperature updates over telemetry. + // This also sends the value of max temperature since last reboot over property update. + private async Task SendTemperatureAsync() + { + await SendTemperatureTelemetryAsync(); + + double maxTemp = _temperatureReadingsDateTimeOffset.Values.Max(); + if (maxTemp > _maxTemp) + { + _maxTemp = maxTemp; + await UpdateMaxTemperatureSinceLastRebootPropertyAsync(); + } + } + + // Send temperature update over telemetry. + private async Task SendTemperatureTelemetryAsync() + { + const string telemetryName = "temperature"; + + using var telemetryMessage = new TelemetryMessage + { + Telemetry = { [telemetryName] = _temperature } + }; + await _deviceClient.SendTelemetryAsync(telemetryMessage); + + _logger.LogDebug($"Telemetry: Sent - {telemetryMessage.Telemetry.GetSerializedString()}."); + _temperatureReadingsDateTimeOffset.Add(DateTimeOffset.Now, _temperature); + } + + // Send temperature over reported property update. + private async Task UpdateMaxTemperatureSinceLastRebootPropertyAsync() + { + const string propertyName = "maxTempSinceLastReboot"; + var reportedProperties = new ClientPropertyCollection(); + reportedProperties.Add(propertyName, _maxTemp); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties); + + _logger.LogDebug($"Property: Update - {reportedProperties.GetSerializedString()} is {nameof(StatusCodes.OK)} " + + $"with a version of {updateResponse.Version}."); + } + + private static double GenerateTemperatureWithinRange(int max = 50, int min = 0) + { + return Math.Round(s_random.NextDouble() * (max - min) + min, 1); + } + } +} diff --git a/iothub/device/samples/convention-based-samples/readme.md b/iothub/device/samples/convention-based-samples/readme.md new file mode 100644 index 0000000000..578c1b72c6 --- /dev/null +++ b/iothub/device/samples/convention-based-samples/readme.md @@ -0,0 +1,58 @@ +--- +page_type: sample +description: "A set of samples that show how a device that uses the IoT Plug and Play conventions interacts with either IoT Hub or IoT Central." +languages: +- csharp +products: +- azure +- azure-iot-hub +- azure-iot-central +- azure-iot-pnp +- dotnet +urlFragment: azure-iot-pnp-device-samples-for-csharp-net +--- + +# IoT Plug And Play device samples + +These samples demonstrate how a device that follows the [IoT Plug and Play conventions][pnp-convention] interacts with IoT Hub or IoT Central, to: + +- Send telemetry. +- Update read-only and read-write properties. +- Respond to command invocation. + +The samples demonstrate two scenarios: + +- An IoT Plug and Play device that implements the [Thermostat][d-thermostat] model. This model has a single interface that defines telemetry, read-only and read-write properties, and commands. +- An IoT Plug and Play device that implements the [Temperature controller][d-temperature-controller] model. This model uses multiple components: + - The top-level interface defines telemetry, read-only property and commands. + - The model includes two [Thermostat][thermostat-model] components, and a [device information][d-device-info] component. + +## Configuring the samples in Visual Studio + +These samples use the `launchSettings.json` in Visual Studio for different configuration settings, one for direct connection strings and one for the Device Provisioning Service (DPS). + +The configuration file is committed to the repository as `launchSettings.template.json`. Rename the file to `launchSettings.json` and then configure it from the **Debug** tab in the project properties. + +## Configuring the samples in VSCode + +These samples use the `launch.json` in Visual Studio Code for different configuration settings, one for direct connection strings and one for DPS. + +The configuration file is committed to the repository as `launch.template.json`. Rename it to `launch.json` to take effect when you start a debugging session. + +## Quickstarts and tutorials + +To learn more about how to configure and run the Thermostat device sample with IoT Hub, see [Quickstart: Connect a sample IoT Plug and Play device application running on Linux or Windows to IoT Hub][thermostat-hub-qs]. + +To learn more about how to configure and run the Temperature Controller device sample with: + +- IoT Hub, see [Tutorial: Connect an IoT Plug and Play multiple component device application running on Linux or Windows to IoT Hub][temp-controller-hub-tutorial] +- IoT Central, see [Tutorial: Create and connect a client application to your Azure IoT Central application][temp-controller-central-tutorial] + +[pnp-convention]: https://docs.microsoft.com/azure/iot-pnp/concepts-convention +[d-thermostat]: ./Thermostat +[d-temperature-controller]: ./TemperatureController +[thermostat-model]: /iot-hub/Samples/device/convention-based-samples/Thermostat/Models/Thermostat.json +[d-device-info]: https://devicemodels.azure.com/dtmi/azure/devicemanagement/deviceinformation-1.json +[thermostat-hub-qs]: https://docs.microsoft.com/azure/iot-pnp/quickstart-connect-device?pivots=programming-language-csharp +[temp-controller-hub-tutorial]: https://docs.microsoft.com/azure/iot-pnp/tutorial-multiple-components?pivots=programming-language-csharp +[temp-controller-central-tutorial]: https://docs.microsoft.com/azure/iot-central/core/tutorial-connect-device?pivots=programming-language-csharp diff --git a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs index f1657f6b1e..91c32fbace 100644 --- a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs +++ b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs @@ -526,7 +526,7 @@ private async Task DisposeMessageAsync(string lockToken, AmqpIotDisposeActions o public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + - "Note that you can still retrieve a client's properties using the legacy DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + + "Note that you can still retrieve a client's properties using DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + "ModuleClient.GetTwinAsync(CancellationToken cancellationToken) operations, but the properties will not be formatted " + "as per DTDL terminology."); } @@ -534,7 +534,7 @@ public override Task GetPropertiesAsync(PayloadConvention payl public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) { throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + - "Note that you can still retrieve a client's properties using the legacy DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + + "Note that you can still retrieve a client's properties using DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + "ModuleClient.GetTwinAsync(CancellationToken cancellationToken) operations, but the properties will not be formatted " + "as per DTDL terminology."); } From 36125ee5d10ea73b70780cb04bdc8a415726d31b Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 25 May 2021 16:58:07 -0700 Subject: [PATCH 09/77] fix(doc, samples): Update API design doc and move SystemTextJson helper to samples --- .../devdoc/Convention-based operations.md | 41 ++++++++++++++----- .../SystemTextJsonWritablePropertyResponse.cs | 7 +--- 2 files changed, 32 insertions(+), 16 deletions(-) rename {shared/src => iothub/device/samples/convention-based-samples/TemperatureController}/SystemTextJsonWritablePropertyResponse.cs (96%) diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index 1af4489ab9..83c3c0ab08 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -2,6 +2,12 @@ #### Common +```diff +public class ClientOptions { ++ public PayloadConvention PayloadConvention { get; set; } +} +``` + ```csharp public abstract class PayloadConvention { @@ -24,7 +30,7 @@ public abstract class PayloadSerializer { public abstract IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null); public abstract T DeserializeToType(string stringToDeserialize); public abstract string SerializeToString(object objectToSerialize); - public abstract bool TryGetNestedObjectValue(object objectToConvert, string propertyName, out T outValue); + public abstract bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue); } public sealed class DefaultPayloadConvention : PayloadConvention { @@ -49,7 +55,7 @@ public class NewtonsoftJsonPayloadSerializer : PayloadSerializer { public override IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null); public override T DeserializeToType(string stringToDeserialize); public override string SerializeToString(object objectToSerialize); - public override bool TryGetNestedObjectValue(object objectToConvert, string propertyName, out T outValue); + public override bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue); } public abstract class PayloadCollection : IEnumerable, IEnumerable { @@ -69,6 +75,7 @@ public abstract class PayloadCollection : IEnumerable, IEnumerable { } public static class ConventionBasedConstants { + public const char ComponentLevelCommandSeparator = '*'; public const string AckCodePropertyName = "ac"; public const string AckDescriptionPropertyName = "ad"; public const string AckVersionPropertyName = "av"; @@ -76,6 +83,14 @@ public static class ConventionBasedConstants { public const string ComponentIdentifierValue = "c"; public const string ValuePropertyName = "value"; } + +public class StatusCodes { + public StatusCodes(); + public static int Accepted { get; } + public static int BadRequest { get; } + public static int NotFound { get; } + public static int OK { get; } +} ``` ### Properties @@ -109,20 +124,25 @@ public Task SubscribeToWritablePropertiesEventAsync(Func properties, string componentName = null); + public void Add(IDictionary properties); + public void Add(string componentName, IDictionary properties); public override void Add(string propertyName, object propertyValue); - public void Add(string propertyName, object propertyValue, int statusCode, long version, string description = null, string componentName = null); - public void Add(string propertyName, object propertyValue, string componentName); - public void AddOrUpdate(IDictionary properties, string componentNam = null); + public void Add(string propertyName, object propertyValue, int statusCode, long version, string description = null); + public void Add(string componentName, string propertyName, object propertyValue); + public void Add(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = null); + public void AddOrUpdate(IDictionary properties); + public void AddOrUpdate(string componentName, IDictionary properties); public override void AddOrUpdate(string propertyName, object propertyValue); - public void AddOrUpdate(string propertyName, object propertyValue, int statusCode, long version, string description = null, string componentName = null); - public void AddOrUpdate(string propertyName, object propertyValue, string componentName); + public void AddOrUpdate(string propertyName, object propertyValue, int statusCode, long version, string description = null); + public void AddOrUpdate(string componentName, string propertyName, object propertyValue); + public void AddOrUpdate(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = null); public bool Contains(string componentName, string propertyName); public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue); } @@ -172,10 +192,8 @@ public class TelemetryCollection : PayloadCollection { public override void AddOrUpdate(string telemetryName, object telemetryValue); } -public class TelemetryMessage : Message { +public sealed class TelemetryMessage : MessageBase { public TelemetryMessage(string componentName = null); - public new string ContentEncoding { get; internal set; } - public new string ContentType { get; internal set; } public TelemetryCollection Telemetry { get; set; } public override Stream GetBodyStream(); } @@ -200,6 +218,7 @@ public sealed class CommandRequest { public string ComponentName { get; private set; } public string DataAsJson { get; } public T GetData(); + public byte[] GetDataAsBytes(); } public sealed class CommandResponse { diff --git a/shared/src/SystemTextJsonWritablePropertyResponse.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs similarity index 96% rename from shared/src/SystemTextJsonWritablePropertyResponse.cs rename to iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs index 8873736908..d0bb306ed7 100644 --- a/shared/src/SystemTextJsonWritablePropertyResponse.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#if !NET451 - using System.Text.Json.Serialization; +using Microsoft.Azure.Devices.Shared; -namespace Microsoft.Azure.Devices.Shared +namespace Microsoft.Azure.Devices.Client.Samples { /// /// An optional, helper class for constructing a writable property response. @@ -56,5 +55,3 @@ public SystemTextJsonWritablePropertyResponse(object propertyValue, int ackCode, public string AckDescription { get; set; } } } - -#endif From 739a9df62be838661eb78d0e791e4ab425b2c090 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 3 Jun 2021 15:16:31 -0700 Subject: [PATCH 10/77] fix(iot-device): Separate out root-level and component-level property addition operations --- e2e/test/iothub/command/CommandE2ETests.cs | 10 +- .../devdoc/Convention-based operations.md | 17 +- .../TemperatureControllerSample.cs | 13 +- .../Thermostat/ThermostatSample.cs | 13 +- iothub/device/src/ClientPropertyCollection.cs | 335 ++++++++---------- .../DeviceClient.ConventionBasedOperations.cs | 6 + .../ModuleClient.ConventionBasedOperations.cs | 6 + iothub/device/src/NumericHelpers.cs | 45 +++ iothub/device/src/PayloadCollection.cs | 32 +- iothub/device/src/TelemetryCollection.cs | 3 +- 10 files changed, 258 insertions(+), 222 deletions(-) create mode 100644 iothub/device/src/NumericHelpers.cs diff --git a/e2e/test/iothub/command/CommandE2ETests.cs b/e2e/test/iothub/command/CommandE2ETests.cs index 07606a5225..02bea0171d 100644 --- a/e2e/test/iothub/command/CommandE2ETests.cs +++ b/e2e/test/iothub/command/CommandE2ETests.cs @@ -2,12 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Diagnostics.Tracing; -using System.Net; -using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Devices.Client; -using Microsoft.Azure.Devices.Common.Exceptions; using Microsoft.Azure.Devices.E2ETests.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -77,7 +73,7 @@ public static async Task DigitalTwinsSendCommandAndVerifyResponseAsync(string de ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Invoke command {commandName}."); - + CloudToDeviceMethodResult serviceClientResponse = null; if (string.IsNullOrEmpty(componentName)) { @@ -130,7 +126,6 @@ await digitalTwinClient.InvokeComponentCommandAsync( logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Command status: {statusCode}."); Assert.AreEqual(200, statusCode, $"The expected response status should be 200 but was {statusCode}"); Assert.AreEqual(responseExpected, payloadReceived, $"The expected response payload should be {responseExpected} but was {payloadReceived}"); - } public static async Task SetDeviceReceiveCommandAsync(DeviceClient deviceClient, string componentName, string commandName, MsTestLogger logger) @@ -152,7 +147,6 @@ await deviceClient.SubscribeToCommandsAsync( else { Assert.AreEqual(componentName, request.ComponentName, $"The expected component name should be {componentName} but was {request.ComponentName}"); - } var assertionObject = new ServiceCommandRequestAssertion(); string responseExpected = JsonConvert.SerializeObject(assertionObject); @@ -195,4 +189,4 @@ await Task await deviceClient.CloseAsync().ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index 83c3c0ab08..0e8d97735a 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -65,6 +65,7 @@ public abstract class PayloadCollection : IEnumerable, IEnumerable { public virtual object this[string key] { get; set; } public virtual void Add(string key, object value); public virtual void AddOrUpdate(string key, object value); + public void ClearCollection(); public bool Contains(string key); public IEnumerator GetEnumerator(); public virtual byte[] GetPayloadObjectBytes(); @@ -132,17 +133,13 @@ public class ClientPropertyCollection : PayloadCollection { public ClientPropertyCollection(); public long Version { get; protected set; } public void Add(IDictionary properties); - public void Add(string componentName, IDictionary properties); - public override void Add(string propertyName, object propertyValue); - public void Add(string propertyName, object propertyValue, int statusCode, long version, string description = null); - public void Add(string componentName, string propertyName, object propertyValue); - public void Add(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = null); + public void AddComponentProperties(string componentName, IDictionary properties); + public void AddComponentProperty(string componentName, string propertyName, object propertyValue); public void AddOrUpdate(IDictionary properties); - public void AddOrUpdate(string componentName, IDictionary properties); - public override void AddOrUpdate(string propertyName, object propertyValue); - public void AddOrUpdate(string propertyName, object propertyValue, int statusCode, long version, string description = null); - public void AddOrUpdate(string componentName, string propertyName, object propertyValue); - public void AddOrUpdate(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = null); + public void AddOrUpdateComponentProperties(string componentName, IDictionary properties); + public void AddOrUpdateComponentProperty(string componentName, string propertyName, object propertyValue); + public void AddOrUpdateRootProperty(string propertyName, object propertyValue); + public void AddRootProperty(string propertyName, object propertyValue); public bool Contains(string componentName, string propertyName); public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue); } diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs index fad873354b..a4d3ee42be 100644 --- a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs @@ -129,8 +129,13 @@ private async Task HandleTargetTemperatureUpdateRequestAsync(string componentNam _logger.LogDebug($"Property: Received - component=\"{componentName}\", [ \"{targetTemperatureProperty}\": {targetTemperature}°C ]."); _temperature[componentName] = targetTemperature; + IWritablePropertyResponse writableResponse = _deviceClient + .PayloadConvention + .PayloadSerializer + .CreateWritablePropertyResponse(_temperature[componentName], StatusCodes.OK, version, "Successfully updated target temperature."); + var reportedProperty = new ClientPropertyCollection(); - reportedProperty.Add(componentName, targetTemperatureProperty, _temperature[componentName], StatusCodes.Accepted, version, "Successfully updated target temperature."); + reportedProperty.AddComponentProperty(componentName, targetTemperatureProperty, writableResponse); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); @@ -288,7 +293,7 @@ private async Task UpdateDeviceInformationPropertyAsync(CancellationToken cancel { "totalMemory", 1024 }, }; var deviceInformation = new ClientPropertyCollection(); - deviceInformation.Add(componentName, deviceInformationProperties); + deviceInformation.AddComponentProperties(componentName, deviceInformationProperties); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(deviceInformation, cancellationToken); @@ -330,7 +335,7 @@ private async Task SendDeviceSerialNumberPropertyIfNotCurrentAsync(CancellationT || serialNumberReported != currentSerialNumber) { var reportedProperties = new ClientPropertyCollection(); - reportedProperties.Add(serialNumber, currentSerialNumber); + reportedProperties.AddRootProperty(serialNumber, currentSerialNumber); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties, cancellationToken); @@ -391,7 +396,7 @@ private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName const string propertyName = "maxTempSinceLastReboot"; double maxTemp = _maxTemp[componentName]; var reportedProperties = new ClientPropertyCollection(); - reportedProperties.Add(componentName, propertyName, maxTemp); + reportedProperties.AddComponentProperty(componentName, propertyName, maxTemp); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties, cancellationToken); diff --git a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs index 73d6e0c072..d59d4bd3ab 100644 --- a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs +++ b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs @@ -74,13 +74,18 @@ private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableP switch (writableProperty.Key) { case "targetTemperature": - const string tagetTemperatureProperty = "targetTemperature"; + const string targetTemperatureProperty = "targetTemperature"; double targetTemperatureRequested = Convert.ToDouble(writableProperty.Value); - _logger.LogDebug($"Property: Received - [ \"{tagetTemperatureProperty}\": {targetTemperatureRequested}°C ]."); + _logger.LogDebug($"Property: Received - [ \"{targetTemperatureProperty}\": {targetTemperatureRequested}°C ]."); _temperature = targetTemperatureRequested; + IWritablePropertyResponse writableResponse = _deviceClient + .PayloadConvention + .PayloadSerializer + .CreateWritablePropertyResponse(_temperature, StatusCodes.OK, writableProperties.Version, "Successfully updated target temperature"); + var reportedProperty = new ClientPropertyCollection(); - reportedProperty.Add(tagetTemperatureProperty, _temperature, StatusCodes.OK, writableProperties.Version, "Successfully updated target temperature"); + reportedProperty.AddRootProperty(targetTemperatureProperty, writableResponse); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); @@ -184,7 +189,7 @@ private async Task UpdateMaxTemperatureSinceLastRebootPropertyAsync() { const string propertyName = "maxTempSinceLastReboot"; var reportedProperties = new ClientPropertyCollection(); - reportedProperties.Add(propertyName, _maxTemp); + reportedProperties.AddRootProperty(propertyName, _maxTemp); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties); diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index cd6737e15a..45aef54502 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client @@ -14,126 +15,104 @@ public class ClientPropertyCollection : PayloadCollection { private const string VersionName = "$version"; - /// /// /// Adds the value to the collection. /// /// - /// If the collection has a key that matches the property name this method will throw an . + /// If the collection already has a key matching a property name supplied this method will throw an . /// /// When using this as part of the writable property flow to respond to a writable property update you should pass in the value /// as an instance of /// to ensure the correct formatting is applied when the object is serialized. /// /// - /// is null /// The name of the property to add. /// The value of the property to add. - public override void Add(string propertyName, object propertyValue) - => Add(null, propertyName, propertyValue); + /// is null. + /// already exists in the collection. + public void AddRootProperty(string propertyName, object propertyValue) + => AddInternal(new Dictionary { { propertyName, propertyValue } }, null, false); - /// + /// /// - /// + /// + /// /// /// Adds the value to the collection. /// - /// is null /// The component with the property to add. /// The name of the property to add. /// The value of the property to add. - public void Add(string componentName, string propertyName, object propertyValue) + public void AddComponentProperty(string componentName, string propertyName, object propertyValue) => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, false); - /// + /// /// - /// + /// + /// /// - /// Adds the values to the collection. + /// Adds the value to the collection. /// - /// A collection of properties to add or update. - public void Add(IDictionary properties) - => AddInternal(properties, null, false); + /// The component with the properties to add. + /// A collection of properties to add. + public void AddComponentProperties(string componentName, IDictionary properties) + => AddInternal(properties, componentName, true); - /// /// - /// + /// + /// /// /// Adds the values to the collection. /// - /// A collection of properties to add or update. - /// The component with the properties to add or update. - public void Add(string componentName, IDictionary properties) - => AddInternal(properties, componentName, false); - - /// - /// - /// - /// Adds a writable property to the collection. - /// - /// - /// This method will use the method to create an instance of that will be properly serialized. - /// - /// is null - /// The name of the property to add or update. - /// The value of the property to add or update. - /// - /// - /// - public void Add(string propertyName, object propertyValue, int statusCode, long version, string description = default) - => Add(null, propertyName, propertyValue, statusCode, version, description); - - /// - /// - /// - /// Adds a writable property to the collection. - /// /// - /// This method will use the method to create an instance of that will be properly serialized. + /// If the collection already has a key matching a property name supplied this method will throw an . + /// + /// When using this as part of the writable property flow to respond to a writable property update you should pass in the value + /// as an instance of + /// to ensure the correct formatting is applied when the object is serialized. + /// + /// + /// This method directly adds the supplied to the collection. + /// For component-level properties, either ensure that you include the component identifier markers {"__t": "c"} as a part of the supplied , + /// or use the convenience method instead. + /// For more information see . + /// /// - /// is null - /// The name of the property to add or update. - /// The value of the property to add or update. - /// - /// - /// - /// - public void Add(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = default) + /// A collection of properties to add. + public void Add(IDictionary properties) { - if (Convention?.PayloadSerializer == null) - { - Add(componentName, propertyName, new { value = propertyValue, ac = statusCode, av = version, ad = description }); - } - else + if (properties == null) { - Add(componentName, propertyName, Convention.PayloadSerializer.CreateWritablePropertyResponse(propertyValue, statusCode, version, description)); + throw new ArgumentNullException(nameof(properties)); } + + properties + .ToList() + .ForEach(entry => Collection.Add(entry.Key, entry.Value)); } /// - /// - /// + /// /// - /// is null + /// /// The name of the property to add or update. /// The value of the property to add or update. - public override void AddOrUpdate(string propertyName, object propertyValue) - => AddOrUpdate(null, propertyName, propertyValue); + public void AddOrUpdateRootProperty(string propertyName, object propertyValue) + => AddInternal(new Dictionary { { propertyName, propertyValue } }, null, true); /// - /// - /// + /// /// - /// is null + /// /// The component with the property to add or update. /// The name of the property to add or update. /// The value of the property to add or update. - public void AddOrUpdate(string componentName, string propertyName, object propertyValue) + public void AddOrUpdateComponentProperty(string componentName, string propertyName, object propertyValue) => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); /// - /// /// + /// /// /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. /// @@ -142,9 +121,10 @@ public void AddOrUpdate(string componentName, string propertyName, object proper /// to ensure the correct formatting is applied when the object is serialized. /// /// + /// The component with the properties to add or update. /// A collection of properties to add or update. - public void AddOrUpdate(IDictionary properties) - => AddInternal(properties, null, true); + public void AddOrUpdateComponentProperties(string componentName, IDictionary properties) + => AddInternal(properties, componentName, true); /// /// @@ -152,126 +132,22 @@ public void AddOrUpdate(IDictionary properties) /// /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. /// - /// When using this as part of the writable property flow to respond to a writable property update - /// you should pass in the value as an instance of + /// When using this as part of the writable property flow to respond to a writable property update you should pass in the value + /// as an instance of /// to ensure the correct formatting is applied when the object is serialized. /// + /// + /// This method directly adds or updates the supplied to the collection. + /// For component-level properties, either ensure that you include the component identifier markers {"__t": "c"} as a part of the supplied , + /// or use the convenience method instead. + /// For more information see . + /// /// - /// The component with the properties to add or update. - /// A collection of properties to add or update. - public void AddOrUpdate(string componentName, IDictionary properties) - => AddInternal(properties, componentName, true); - - /// - /// - /// - /// Adds or updates a type of to the collection. - /// - /// is null - /// The name of the writable property to add or update. - /// The value of the writable property to add or update. - /// - /// - /// - public void AddOrUpdate(string propertyName, object propertyValue, int statusCode, long version, string description = default) - => AddOrUpdate(null, propertyName, propertyValue, statusCode, version, description); - - /// - /// - /// - /// - /// Adds or updates a type of to the collection. - /// - /// is null - /// The name of the writable property to add or update. - /// The value of the writable property to add or update. - /// - /// - /// - /// - public void AddOrUpdate(string componentName, string propertyName, object propertyValue, int statusCode, long version, string description = default) - { - if (Convention?.PayloadSerializer == null) - { - AddOrUpdate(componentName, propertyName, new { value = propertyValue, ac = statusCode, av = version, ad = description }); - } - else - { - AddOrUpdate(componentName, propertyName, Convention.PayloadSerializer.CreateWritablePropertyResponse(propertyValue, statusCode, version, description)); - } - } - - /// - /// Adds or updates the value for the collection. - /// - /// - /// - /// /// A collection of properties to add or update. - /// The component with the properties to add or update. - /// Forces the collection to use the Add or Update behavior. - /// Setting to true will simply overwrite the value. Setting to false will use - private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) - { - if (properties == null) - { - throw new ArgumentNullException(nameof(properties)); - } - - // If the componentName is null then simply add the key-value pair to Collection dictionary. - // this will either insert a property or overwrite it if it already exists. - if (componentName == null) - { - foreach (KeyValuePair entry in properties) - { - if (forceUpdate) - { - Collection[entry.Key] = entry.Value; - } - else - { - Collection.Add(entry.Key, entry.Value); - } - } - } - else - { - // If the component name already exists within the dictionary, then the value is a dictionary containing the component level property key and values. - // Append this property dictionary to the existing property value dictionary (overwrite entries if they already exist). - // Otherwise, add this as a new entry. - var componentProperties = new Dictionary(); - if (Collection.ContainsKey(componentName)) - { - componentProperties = (Dictionary)Collection[componentName]; - } - foreach (KeyValuePair entry in properties) - { - if (forceUpdate) - { - componentProperties[entry.Key] = entry.Value; - } - else - { - componentProperties.Add(entry.Key, entry.Value); - } - } - - // For a component level property, the property patch needs to contain the {"__t": "c"} component identifier. - if (!componentProperties.ContainsKey(ConventionBasedConstants.ComponentIdentifierKey)) - { - componentProperties[ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue; - } - - if (forceUpdate) - { - Collection[componentName] = componentProperties; - } - else - { - Collection.Add(componentName, componentProperties); - } - } - } + public void AddOrUpdate(IDictionary properties) + => properties + .ToList() + .ForEach(entry => Collection[entry.Key] = entry.Value); /// /// Determines whether the specified property is present. @@ -323,14 +199,27 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou if (componentProperties is IDictionary nestedDictionary) { - if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement) && dictionaryElement is T valueRef) + if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement)) { - propertyValue = valueRef; - return true; + // If the value is null, go ahead and return it. + if (dictionaryElement == null) + { + propertyValue = default; + return true; + } + + // If the object is of type T or can be cast to type T, go ahead and return it. + if (dictionaryElement is T valueRef + || NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) + { + propertyValue = valueRef; + return true; + } } } else { + // If it's not, we need to try to convert it using the serializer. Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); return true; } @@ -397,5 +286,71 @@ internal static ClientPropertyCollection FromClientTwinDictionary(IDictionary + /// Adds or updates the value for the collection. + /// + /// + /// + /// + /// A collection of properties to add or update. + /// The component with the properties to add or update. + /// Forces the collection to use the Add or Update behavior. + /// Setting to true will simply overwrite the value. Setting to false will use + /// is null. + private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) + { + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + + // If the componentName is null then simply add the key-value pair to Collection dictionary. + // This will either insert a property or overwrite it if it already exists. + if (componentName == null) + { + foreach (KeyValuePair entry in properties) + { + if (forceUpdate) + { + Collection[entry.Key] = entry.Value; + } + else + { + Collection.Add(entry.Key, entry.Value); + } + } + } + else + { + // If the component name already exists within the dictionary, then the value is a dictionary containing the component level property key and values. + // Append this property dictionary to the existing property value dictionary (overwrite entries if they already exist, if forceUpdate is true). + // Otherwise, if the component name does not exist in the dictionary, then add this as a new entry. + var componentProperties = new Dictionary(); + if (Collection.ContainsKey(componentName)) + { + componentProperties = (Dictionary)Collection[componentName]; + } + foreach (KeyValuePair entry in properties) + { + if (forceUpdate) + { + componentProperties[entry.Key] = entry.Value; + } + else + { + componentProperties.Add(entry.Key, entry.Value); + } + } + + // For a component level property, the property patch needs to contain the {"__t": "c"} component identifier. + if (!componentProperties.ContainsKey(ConventionBasedConstants.ComponentIdentifierKey)) + { + componentProperties[ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue; + } + + Collection[componentName] = componentProperties; + } + } } } diff --git a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs index 7931f3ff92..3c1e63468d 100644 --- a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs +++ b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client { @@ -14,6 +15,11 @@ namespace Microsoft.Azure.Devices.Client /// public partial class DeviceClient : IDisposable { + /// + /// The that the client uses for convention-based operations. + /// + public PayloadConvention PayloadConvention => InternalClient.PayloadConvention; + /// /// Send telemetry using the specified message. /// diff --git a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs index d7e16690b5..8f8e7eedfb 100644 --- a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs +++ b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client { @@ -14,6 +15,11 @@ namespace Microsoft.Azure.Devices.Client /// public partial class ModuleClient : IDisposable { + /// + /// The that the client uses for convention-based operations. + /// + public PayloadConvention PayloadConvention => InternalClient.PayloadConvention; + /// /// Send telemetry using the specified message. /// diff --git a/iothub/device/src/NumericHelpers.cs b/iothub/device/src/NumericHelpers.cs new file mode 100644 index 0000000000..d9b836c90e --- /dev/null +++ b/iothub/device/src/NumericHelpers.cs @@ -0,0 +1,45 @@ +// 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.Globalization; + +namespace Microsoft.Azure.Devices.Client +{ + internal class NumericHelpers + { + internal static bool TryCastNumericTo(object input, out T result) + { + if (TryGetNumeric(input)) + { + try + { + result = (T)Convert.ChangeType(input, typeof(T), CultureInfo.InvariantCulture); + return true; + } + catch + { + } + } + + result = default; + return false; + } + + private static bool TryGetNumeric(object expression) + { + if (expression == null) + { + return false; + } + + return double.TryParse( + Convert.ToString( + expression, + CultureInfo.InvariantCulture), + NumberStyles.Any, + NumberFormatInfo.InvariantInfo, + out _); + } + } +} diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index 4710157b06..2faaca58c0 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -45,6 +45,10 @@ public virtual object this[string key] /// /// Adds the key-value pair to the collection. /// + /// + /// For property operations see + /// and instead. + /// /// /// /// @@ -57,6 +61,10 @@ public virtual void Add(string key, object value) /// /// Adds or updates the key-value pair to the collection. /// + /// + /// For property operations see + /// and instead. + /// /// The name of the telemetry. /// The value of the telemetry. /// is null. @@ -108,14 +116,22 @@ public bool TryGetValue(string key, out T value) if (Collection.ContainsKey(key)) { - // If the object is of type T go ahead and return it. - if (Collection[key] is T valueRef) + // If the value is null, go ahead and return it. + if (Collection[key] == null) + { + value = default; + return true; + } + + // If the object is of type T or can be cast to type T, go ahead and return it. + if (Collection[key] is T valueRef + || NumericHelpers.TryCastNumericTo(Collection[key], out valueRef)) { value = valueRef; return true; } - // If it's not we need to try to convert it using the serializer. - // JObject or JsonElement + + // If it's not, we need to try to convert it using the serializer. value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); return true; } @@ -133,6 +149,14 @@ public virtual string GetSerializedString() return Convention.PayloadSerializer.SerializeToString(Collection); } + /// + /// Remove all items from the collection. + /// + public void ClearCollection() + { + Collection.Clear(); + } + /// public IEnumerator GetEnumerator() { diff --git a/iothub/device/src/TelemetryCollection.cs b/iothub/device/src/TelemetryCollection.cs index 677a6d16e0..99d0164aff 100644 --- a/iothub/device/src/TelemetryCollection.cs +++ b/iothub/device/src/TelemetryCollection.cs @@ -2,12 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client { /// - /// The telmetry collection used to populate a . + /// The telemetry collection used to populate a . /// public class TelemetryCollection : PayloadCollection { From 3c14bcdfb8009bac029a90cbf14b970638754bc7 Mon Sep 17 00:00:00 2001 From: jamdavi <73593426+jamdavi@users.noreply.github.com> Date: Fri, 4 Jun 2021 13:21:04 -0400 Subject: [PATCH 11/77] feat(tests): Add unit tests for ClientPropertyCollection feat(tests): Add unit tests for ClientPropertyCollection Co-authored-by: Abhipsa Misra --- .../SystemTextJsonWritablePropertyResponse.cs | 6 +- .../tests/ClientPropertyCollectionTests.cs | 320 ++++++++++++++++++ ...ClientPropertyCollectionTestsNewtonsoft.cs | 284 ++++++++++++++++ iothub/device/tests/NumericHelpersTests.cs | 36 ++ shared/src/NewtonsoftJsonPayloadSerializer.cs | 10 +- shared/src/TwinCollection.cs | 48 +-- 6 files changed, 672 insertions(+), 32 deletions(-) create mode 100644 iothub/device/tests/ClientPropertyCollectionTests.cs create mode 100644 iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs create mode 100644 iothub/device/tests/NumericHelpersTests.cs diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs index d0bb306ed7..97a8a84d64 100644 --- a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs @@ -18,13 +18,13 @@ public sealed class SystemTextJsonWritablePropertyResponse : IWritablePropertyRe /// /// Convenience constructor for specifying the properties. /// - /// The unserialized property value. + /// The unserialized property value. /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. /// The acknowledgment version, as supplied in the property update request. /// The acknowledgment description, an optional, human-readable message about the result of the property update. - public SystemTextJsonWritablePropertyResponse(object propertyValue, int ackCode, long ackVersion, string ackDescription = default) + public SystemTextJsonWritablePropertyResponse(object value, int ackCode, long ackVersion, string ackDescription = default) { - Value = propertyValue; + Value = value; AckCode = ackCode; AckVersion = ackVersion; AckDescription = ackDescription; diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs new file mode 100644 index 0000000000..71c58426c2 --- /dev/null +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -0,0 +1,320 @@ +// 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 FluentAssertions; +using Microsoft.Azure.Devices.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.Client.Tests +{ + [TestClass] + [TestCategory("Unit")] + public class ClientPropertyCollectionTests + { + private const string BoolPropertyName = "boolProperty"; + private const string DoublePropertyName = "doubleProperty"; + private const string FloatPropertyName = "floatProperty"; + private const string IntPropertyName = "intProperty"; + private const string ShortPropertyName = "shortProperty"; + private const string StringPropertyName = "stringPropertyName"; + private const string ObjectPropertyName = "objectPropertyName"; + private const string ArrayPropertyName = "arrayPropertyName"; + private const string MapPropertyName = "mapPropertyName"; + private const string DateTimePropertyName = "dateTimePropertyName"; + + private const bool BoolPropertyValue = false; + private const double DoublePropertyValue = 1.001; + private const float FloatPropertyValue = 1.2f; + private const int IntPropertyValue = 12345678; + private const short ShortPropertyValue = 1234; + private const string StringPropertyValue = "propertyValue"; + + private const string ComponentName = "testableComponent"; + private const string WritablePropertyDescription = "testableWritablePropertyDescription"; + private const string UpdatedPropertyValue = "updatedPropertyValue"; + + private static readonly DateTimeOffset s_dateTimePropertyValue = DateTimeOffset.Now; + private static readonly CustomClientProperty s_objectPropertyValue = new CustomClientProperty { Id = 123, Name = "testName" }; + + private static readonly List s_arrayPropertyValues = new List + { + 1, + "someString", + false, + s_objectPropertyValue + }; + + private static readonly Dictionary s_mapPropertyValues = new Dictionary + { + { "key1", "value1" }, + { "key2", 123 }, + { "key3", s_objectPropertyValue } + }; + + [TestMethod] + public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection + { + { StringPropertyName, StringPropertyValue }, + { BoolPropertyName, BoolPropertyValue }, + { DoublePropertyName, DoublePropertyValue }, + { FloatPropertyName, FloatPropertyValue }, + { IntPropertyName, IntPropertyValue }, + { ShortPropertyName, ShortPropertyValue }, + { ObjectPropertyName, s_objectPropertyValue }, + { ArrayPropertyName, s_arrayPropertyValues }, + { MapPropertyName, s_mapPropertyValues }, + { DateTimePropertyName, s_dateTimePropertyValue } + }; + + clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); + stringOutValue.Should().Be(StringPropertyValue); + + clientProperties.TryGetValue(BoolPropertyName, out bool boolOutValue); + boolOutValue.Should().Be(BoolPropertyValue); + + clientProperties.TryGetValue(DoublePropertyName, out double doubleOutValue); + doubleOutValue.Should().Be(DoublePropertyValue); + + clientProperties.TryGetValue(FloatPropertyName, out float floatOutValue); + floatOutValue.Should().Be(FloatPropertyValue); + + clientProperties.TryGetValue(IntPropertyName, out int intOutValue); + intOutValue.Should().Be(IntPropertyValue); + + clientProperties.TryGetValue(ShortPropertyName, out short shortOutValue); + shortOutValue.Should().Be(ShortPropertyValue); + + clientProperties.TryGetValue(ObjectPropertyName, out CustomClientProperty objectOutValue); + objectOutValue.Id.Should().Be(s_objectPropertyValue.Id); + objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); + + clientProperties.TryGetValue(ArrayPropertyName, out List arrayOutValue); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); + arrayOutValue.Should().BeEquivalentTo(s_arrayPropertyValues); + + clientProperties.TryGetValue(MapPropertyName, out Dictionary mapOutValue); + mapOutValue.Should().HaveSameCount(s_mapPropertyValues); + mapOutValue.Should().BeEquivalentTo(s_mapPropertyValues); + + clientProperties.TryGetValue(DateTimePropertyName, out DateTimeOffset dateTimeOutValue); + dateTimeOutValue.Should().Be(s_dateTimePropertyValue); + } + + [TestMethod] + public void ClientPropertyCollection_AddSimpleObjectAgainThrowsException() + { + var clientProperties = new ClientPropertyCollection + { + { StringPropertyName, StringPropertyValue } + }; + + Action act = () => clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + act.Should().Throw("\"Add\" method does not support adding a key that already exists in the collection."); + } + + [TestMethod] + public void ClientPropertyCollection_CanUpdateSimpleObjectAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection + { + { StringPropertyName, StringPropertyValue } + }; + clientProperties.TryGetValue(StringPropertyName, out string outValue); + outValue.Should().Be(StringPropertyValue); + + clientProperties.AddOrUpdateRootProperty(StringPropertyName, UpdatedPropertyValue); + clientProperties.TryGetValue(StringPropertyName, out string outValueChanged); + outValueChanged.Should().Be(UpdatedPropertyValue, "\"AddOrUpdate\" should overwrite the value if the key already exists in the collection."); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddNullPropertyAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + clientProperties.AddRootProperty(IntPropertyName, null); + + clientProperties.TryGetValue(StringPropertyName, out string outStringValue); + outStringValue.Should().Be(StringPropertyValue); + + bool nullPropertyPresent = clientProperties.TryGetValue(IntPropertyName, out int? outIntValue); + nullPropertyPresent.Should().BeTrue(); + outIntValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddMultiplePropertyAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + clientProperties.AddRootProperty(IntPropertyName, IntPropertyValue); + + clientProperties.TryGetValue(StringPropertyName, out string outStringValue); + outStringValue.Should().Be(StringPropertyValue); + + clientProperties.TryGetValue(IntPropertyName, out int outIntValue); + outIntValue.Should().Be(IntPropertyValue); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection + { + { ComponentName, new Dictionary { + { StringPropertyName, StringPropertyValue }, + { BoolPropertyName, BoolPropertyValue }, + { DoublePropertyName, DoublePropertyValue }, + { FloatPropertyName, FloatPropertyValue }, + { IntPropertyName, IntPropertyValue }, + { ShortPropertyName, ShortPropertyValue }, + { ObjectPropertyName, s_objectPropertyValue }, + { ArrayPropertyName, s_arrayPropertyValues }, + { MapPropertyName, s_mapPropertyValues }, + { DateTimePropertyName, s_dateTimePropertyValue } } + } + }; + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); + stringOutValue.Should().Be(StringPropertyValue); + + clientProperties.TryGetValue(ComponentName, BoolPropertyName, out bool boolOutValue); + boolOutValue.Should().Be(BoolPropertyValue); + + clientProperties.TryGetValue(ComponentName, DoublePropertyName, out double doubleOutValue); + doubleOutValue.Should().Be(DoublePropertyValue); + + clientProperties.TryGetValue(ComponentName, FloatPropertyName, out float floatOutValue); + floatOutValue.Should().Be(FloatPropertyValue); + + clientProperties.TryGetValue(ComponentName, IntPropertyName, out int intOutValue); + intOutValue.Should().Be(IntPropertyValue); + + clientProperties.TryGetValue(ComponentName, ShortPropertyName, out short shortOutValue); + shortOutValue.Should().Be(ShortPropertyValue); + + clientProperties.TryGetValue(ComponentName, ObjectPropertyName, out CustomClientProperty objectOutValue); + objectOutValue.Id.Should().Be(s_objectPropertyValue.Id); + objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); + + clientProperties.TryGetValue(ComponentName, ArrayPropertyName, out List arrayOutValue); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); + arrayOutValue.Should().BeEquivalentTo(s_arrayPropertyValues); + + clientProperties.TryGetValue(ComponentName, MapPropertyName, out Dictionary mapOutValue); + mapOutValue.Should().HaveSameCount(s_mapPropertyValues); + mapOutValue.Should().BeEquivalentTo(s_mapPropertyValues); + + clientProperties.TryGetValue(ComponentName, DateTimePropertyName, out DateTimeOffset dateTimeOutValue); + dateTimeOutValue.Should().Be(s_dateTimePropertyValue); + } + + [TestMethod] + public void ClientPropertyCollection_AddSimpleObjectWithComponentAgainThrowsException() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + Action act = () => clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + act.Should().Throw("\"Add\" method does not support adding a key that already exists in the collection."); + } + + [TestMethod] + public void ClientPropertyCollection_CanUpdateSimpleObjectWithComponentAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); + outValue.Should().Be(StringPropertyValue); + + clientProperties.AddOrUpdateComponentProperty(ComponentName, StringPropertyName, UpdatedPropertyValue); + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValueChanged); + outValueChanged.Should().Be(UpdatedPropertyValue, "\"AddOrUpdate\" should overwrite the value if the key already exists in the collection."); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddNullPropertyWithComponentAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + clientProperties.AddComponentProperty(ComponentName, IntPropertyName, null); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outStringValue); + outStringValue.Should().Be(StringPropertyValue); + + bool nullPropertyPresent = clientProperties.TryGetValue(ComponentName, IntPropertyName, out int? outIntValue); + nullPropertyPresent.Should().BeTrue(); + outIntValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddMultiplePropertyWithComponentAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + clientProperties.AddComponentProperty(ComponentName, IntPropertyName, IntPropertyValue); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outStringValue); + outStringValue.Should().Be(StringPropertyValue); + + clientProperties.TryGetValue(ComponentName, IntPropertyName, out int outIntValue); + outIntValue.Should().Be(IntPropertyValue); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddSimpleWritablePropertyAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + + var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, StatusCodes.OK, 2, WritablePropertyDescription); + clientProperties.AddRootProperty(StringPropertyName, writableResponse); + + clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + outValue.Value.Should().Be(writableResponse.Value); + outValue.AckCode.Should().Be(writableResponse.AckCode); + outValue.AckVersion.Should().Be(writableResponse.AckVersion); + outValue.AckDescription.Should().Be(writableResponse.AckDescription); + } + + [TestMethod] + public void ClientPropertyCollection_CanAddWritablePropertyWithComponentAndGetBackWithoutDeviceClient() + { + var clientProperties = new ClientPropertyCollection(); + + var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, StatusCodes.OK, 2, WritablePropertyDescription); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, writableResponse); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + outValue.Value.Should().Be(writableResponse.Value); + outValue.AckCode.Should().Be(writableResponse.AckCode); + outValue.AckVersion.Should().Be(writableResponse.AckVersion); + outValue.AckDescription.Should().Be(writableResponse.AckDescription); + } + + [TestMethod] + public void ClientPropertyCollection_AddingComponentAddsComponentIdentifier() + { + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); + clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); + + outValue.Should().Be(StringPropertyValue); + componentOut.Should().Be(ConventionBasedConstants.ComponentIdentifierValue); + } + } + + internal class CustomClientProperty + { + // The properties in here need to be public otherwise NewtonSoft.Json cannot serialize and deserialize them properly. + public int Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs new file mode 100644 index 0000000000..fb7c35bf9d --- /dev/null +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -0,0 +1,284 @@ +// 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; +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.Azure.Devices.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Azure.Devices.Client.Tests +{ + [TestClass] + [TestCategory("Unit")] + // These tests test the deserialization of the service response to a ClientPropertyCollection. + // This flow is convention aware and uses NewtonSoft.Json for deserialization. + // For the purpose of these tests we will create an instance of a Twin class to simulate the service response. + public class ClientPropertyCollectionTestsNewtonsoft + { + internal const string BoolPropertyName = "boolProperty"; + internal const string DoublePropertyName = "doubleProperty"; + internal const string FloatPropertyName = "floatProperty"; + internal const string IntPropertyName = "intProperty"; + internal const string ShortPropertyName = "shortProperty"; + internal const string StringPropertyName = "stringPropertyName"; + internal const string ObjectPropertyName = "objectPropertyName"; + internal const string ArrayPropertyName = "arrayPropertyName"; + internal const string MapPropertyName = "mapPropertyName"; + internal const string DateTimePropertyName = "dateTimePropertyName"; + internal const string ComponentName = "testableComponent"; + + private const bool BoolPropertyValue = false; + private const double DoublePropertyValue = 1.001; + private const float FloatPropertyValue = 1.2f; + private const int IntPropertyValue = 12345678; + private const short ShortPropertyValue = 1234; + private const string StringPropertyValue = "propertyValue"; + + private const string UpdatedPropertyValue = "updatedPropertyValue"; + + private static readonly DateTimeOffset s_dateTimePropertyValue = DateTimeOffset.Now; + private static readonly CustomClientProperty s_objectPropertyValue = new CustomClientProperty { Id = 123, Name = "testName" }; + + private static readonly List s_arrayPropertyValues = new List + { + 1, + "someString", + false, + s_objectPropertyValue + }; + + private static readonly Dictionary s_mapPropertyValues = new Dictionary + { + { "key1", "value1" }, + { "key2", 123 }, + { "key3", s_objectPropertyValue } + }; + + // Create an object that represents all of the properties as root-level properties. + private static readonly RootLevelProperties s_rootLevelProperties = new RootLevelProperties + { + BooleanProperty = BoolPropertyValue, + DoubleProperty = DoublePropertyValue, + FloatProperty = FloatPropertyValue, + IntProperty = IntPropertyValue, + ShortProperty = ShortPropertyValue, + StringProperty = StringPropertyValue, + ObjectProperty = s_objectPropertyValue, + ArrayProperty = s_arrayPropertyValues, + MapProperty = s_mapPropertyValues, + DateTimeProperty = s_dateTimePropertyValue + }; + + // Create an object that represents all of the properties as component-level properties. + // This adds the "__t": "c" component identifier as a part of "ComponentProperties" class declaration. + private static readonly ComponentLevelProperties s_componentLevelProperties = new ComponentLevelProperties + { + Properties = new ComponentProperties + { + BooleanProperty = BoolPropertyValue, + DoubleProperty = DoublePropertyValue, + FloatProperty = FloatPropertyValue, + IntProperty = IntPropertyValue, + ShortProperty = ShortPropertyValue, + StringProperty = StringPropertyValue, + ObjectProperty = s_objectPropertyValue, + ArrayProperty = s_arrayPropertyValues, + MapProperty = s_mapPropertyValues, + DateTimeProperty = s_dateTimePropertyValue + }, + }; + + // Create a writable property response with the expected values. + private static readonly IWritablePropertyResponse s_writablePropertyResponse = new NewtonsoftJsonWritablePropertyResponse( + propertyValue: StringPropertyValue, + ackCode: StatusCodes.OK, + ackVersion: 2, + ackDescription: "testableWritablePropertyDescription"); + + // Create a JObject instance that represents a writable property response sent for a root-level property. + private static readonly JObject s_writablePropertyResponseJObject = new JObject( + new JProperty(StringPropertyName, JObject.FromObject(s_writablePropertyResponse))); + + // Create a JObject instance that represents a writable property response sent for a component-level property. + // This adds the "__t": "c" component identifier to the constructed JObject. + private static readonly JObject s_writablePropertyResponseWithComponentJObject = new JObject( + new JProperty(ComponentName, new JObject( + new JProperty(ConventionBasedConstants.ComponentIdentifierKey, ConventionBasedConstants.ComponentIdentifierValue), + new JProperty(StringPropertyName, JObject.FromObject(s_writablePropertyResponse))))); + + // The above constructed json objects are used for initializing a twin response. + // This is because we are using a Twin instance to simulate the service response. + + private static TwinCollection collectionToRoundTrip = new TwinCollection(JsonConvert.SerializeObject(s_rootLevelProperties)); + private static TwinCollection collectionWithComponentToRoundTrip = new TwinCollection(JsonConvert.SerializeObject(s_componentLevelProperties)); + private static TwinCollection collectionWritablePropertyToRoundTrip = new TwinCollection(s_writablePropertyResponseJObject, null); + private static TwinCollection collectionWritablePropertyWithComponentToRoundTrip = new TwinCollection(s_writablePropertyResponseWithComponentJObject, null); + + [TestMethod] + public void ClientPropertyCollectionNewtonsoft_CanGetValue() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + + clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); + stringOutValue.Should().Be(StringPropertyValue); + + clientProperties.TryGetValue(BoolPropertyName, out bool boolOutValue); + boolOutValue.Should().Be(BoolPropertyValue); + + clientProperties.TryGetValue(DoublePropertyName, out double doubleOutValue); + doubleOutValue.Should().Be(DoublePropertyValue); + + clientProperties.TryGetValue(FloatPropertyName, out float floatOutValue); + floatOutValue.Should().Be(FloatPropertyValue); + + clientProperties.TryGetValue(IntPropertyName, out int intOutValue); + intOutValue.Should().Be(IntPropertyValue); + + clientProperties.TryGetValue(ShortPropertyName, out short shortOutValue); + shortOutValue.Should().Be(ShortPropertyValue); + + clientProperties.TryGetValue(ObjectPropertyName, out CustomClientProperty objectOutValue); + objectOutValue.Id.Should().Be(s_objectPropertyValue.Id); + objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); + + // The two lists won't be exactly equal since TryGetValue doesn't implement nested deserialization + // => the complex object inside the list is deserialized to a JObject. + clientProperties.TryGetValue(ArrayPropertyName, out List arrayOutValue); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); + + // The two dictionaries won't be exactly equal since TryGetValue doesn't implement nested deserialization + // => the complex object inside the dictionary is deserialized to a JObject. + clientProperties.TryGetValue(MapPropertyName, out Dictionary mapOutValue); + mapOutValue.Should().HaveSameCount(s_mapPropertyValues); + + clientProperties.TryGetValue(DateTimePropertyName, out DateTimeOffset dateTimeOutValue); + dateTimeOutValue.Should().Be(s_dateTimePropertyValue); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); + stringOutValue.Should().Be(StringPropertyValue); + + clientProperties.TryGetValue(ComponentName, BoolPropertyName, out bool boolOutValue); + boolOutValue.Should().Be(BoolPropertyValue); + + clientProperties.TryGetValue(ComponentName, DoublePropertyName, out double doubleOutValue); + doubleOutValue.Should().Be(DoublePropertyValue); + + clientProperties.TryGetValue(ComponentName, FloatPropertyName, out float floatOutValue); + floatOutValue.Should().Be(FloatPropertyValue); + + clientProperties.TryGetValue(ComponentName, IntPropertyName, out int intOutValue); + intOutValue.Should().Be(IntPropertyValue); + + clientProperties.TryGetValue(ComponentName, ShortPropertyName, out short shortOutValue); + shortOutValue.Should().Be(ShortPropertyValue); + + clientProperties.TryGetValue(ComponentName, ObjectPropertyName, out CustomClientProperty objectOutValue); + objectOutValue.Id.Should().Be(s_objectPropertyValue.Id); + objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); + + // The two lists won't be exactly equal since TryGetValue doesn't implement nested deserialization + // => the complex object inside the list is deserialized to a JObject. + clientProperties.TryGetValue(ComponentName, ArrayPropertyName, out List arrayOutValue); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); + + // The two dictionaries won't be exactly equal since TryGetValue doesn't implement nested deserialization + // => the complex object inside the dictionary is deserialized to a JObject. + clientProperties.TryGetValue(ComponentName, MapPropertyName, out Dictionary mapOutValue); + mapOutValue.Should().HaveSameCount(s_mapPropertyValues); + + clientProperties.TryGetValue(ComponentName, DateTimePropertyName, out DateTimeOffset dateTimeOutValue); + dateTimeOutValue.Should().Be(s_dateTimePropertyValue); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGetBack() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyToRoundTrip, DefaultPayloadConvention.Instance); + + clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + outValue.Value.Should().Be(StringPropertyValue); + outValue.AckCode.Should().Be(s_writablePropertyResponse.AckCode); + outValue.AckVersion.Should().Be(s_writablePropertyResponse.AckVersion); + outValue.AckDescription.Should().Be(s_writablePropertyResponse.AckDescription); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithComponentAndGetBack() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + outValue.Value.Should().Be(StringPropertyValue); + outValue.AckCode.Should().Be(s_writablePropertyResponse.AckCode); + outValue.AckVersion.Should().Be(s_writablePropertyResponse.AckVersion); + outValue.AckDescription.Should().Be(s_writablePropertyResponse.AckDescription); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonsoft_CanGetComponentIdentifier() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); + clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); + + outValue.Should().Be(StringPropertyValue); + componentOut.Should().Be(ConventionBasedConstants.ComponentIdentifierValue); + } + } + + internal class RootLevelProperties + { + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.BoolPropertyName)] + public bool BooleanProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.DoublePropertyName)] + public double DoubleProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.FloatPropertyName)] + public float FloatProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.IntPropertyName)] + public int IntProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ShortPropertyName)] + public short ShortProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.StringPropertyName)] + public string StringProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ObjectPropertyName)] + public object ObjectProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ArrayPropertyName)] + public IList ArrayProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.MapPropertyName)] + public IDictionary MapProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.DateTimePropertyName)] + public DateTimeOffset DateTimeProperty { get; set; } + } + + internal class ComponentProperties : RootLevelProperties + { + [JsonProperty(ConventionBasedConstants.ComponentIdentifierKey)] + public string ComponentIdentifier { get; } = ConventionBasedConstants.ComponentIdentifierValue; + } + + internal class ComponentLevelProperties + { + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentName)] + public ComponentProperties Properties { get; set; } + } +} diff --git a/iothub/device/tests/NumericHelpersTests.cs b/iothub/device/tests/NumericHelpersTests.cs new file mode 100644 index 0000000000..d1f4e72684 --- /dev/null +++ b/iothub/device/tests/NumericHelpersTests.cs @@ -0,0 +1,36 @@ +// 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.Text; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.Client.Tests +{ + [TestClass] + [TestCategory("Unit")] + public class NumericHelpersTests + { + [TestMethod] + public void CanConvertNumericTypes() + { + TestNumericConversion(1.001d, true, 1.001f); + TestNumericConversion(123, true, 123); + TestNumericConversion(123, true, 123); + TestNumericConversion(123, true, 123); + TestNumericConversion(123, true, 123); + TestNumericConversion("someString", false, 0); + TestNumericConversion(true, false, 0); + } + + private void TestNumericConversion(object input, bool canConvertExpected, T resultExpected) + { + bool canConvertActual = NumericHelpers.TryCastNumericTo(input, out T result); + + canConvertActual.Should().Be(canConvertExpected); + result.Should().Be(resultExpected); + } + } +} diff --git a/shared/src/NewtonsoftJsonPayloadSerializer.cs b/shared/src/NewtonsoftJsonPayloadSerializer.cs index 7d90b163a0..b38063178b 100644 --- a/shared/src/NewtonsoftJsonPayloadSerializer.cs +++ b/shared/src/NewtonsoftJsonPayloadSerializer.cs @@ -39,11 +39,11 @@ public override T DeserializeToType(string stringToDeserialize) /// public override T ConvertFromObject(object objectToConvert) { - if (objectToConvert == null) - { - return default; - } - return ((JToken)objectToConvert).ToObject(); + var token = JToken.FromObject(objectToConvert); + + return objectToConvert == null + ? default + : token.ToObject(); } /// diff --git a/shared/src/TwinCollection.cs b/shared/src/TwinCollection.cs index ff8fbb9f4f..8149e1f8fb 100644 --- a/shared/src/TwinCollection.cs +++ b/shared/src/TwinCollection.cs @@ -55,28 +55,28 @@ public TwinCollection(string twinJson, string metadataJson) } /// - /// Creates a using a JSON fragment as the body. + /// Creates a using the given JSON fragments for the body and metadata. /// /// JSON fragment containing the twin data. - internal TwinCollection(JObject twinJson) + /// JSON fragment containing the metadata. + public TwinCollection(JObject twinJson, JObject metadataJson) { JObject = twinJson ?? new JObject(); - - if (JObject.TryGetValue(MetadataName, out JToken metadataJToken)) - { - _metadata = metadataJToken as JObject; - } + _metadata = metadataJson; } /// - /// Creates a using the given JSON fragments for the body and metadata. + /// Creates a using a JSON fragment as the body. /// /// JSON fragment containing the twin data. - /// JSON fragment containing the metadata. - public TwinCollection(JObject twinJson, JObject metadataJson) + internal TwinCollection(JObject twinJson) { JObject = twinJson ?? new JObject(); - _metadata = metadataJson; + + if (JObject.TryGetValue(MetadataName, out JToken metadataJToken)) + { + _metadata = metadataJToken as JObject; + } } /// @@ -121,8 +121,6 @@ public int Count } } - internal JObject JObject { get; private set; } - /// /// Property Indexer /// @@ -227,6 +225,19 @@ public IEnumerator GetEnumerator() } } + /// + /// Clear metadata out of the collection + /// + public void ClearMetadata() + { + TryClearMetadata(MetadataName); + TryClearMetadata(LastUpdatedName); + TryClearMetadata(LastUpdatedVersionName); + TryClearMetadata(VersionName); + } + + internal JObject JObject { get; private set; } + private bool TryGetMemberInternal(string propertyName, out object result) { if (!JObject.TryGetValue(propertyName, out JToken value)) @@ -283,16 +294,5 @@ private void TryClearMetadata(string propertyName) JObject.Remove(propertyName); } } - - /// - /// Clear metadata out of the collection - /// - public void ClearMetadata() - { - TryClearMetadata(MetadataName); - TryClearMetadata(LastUpdatedName); - TryClearMetadata(LastUpdatedVersionName); - TryClearMetadata(VersionName); - } } } From ef1df7072f2090b1decadb339c13b02a4243ae1c Mon Sep 17 00:00:00 2001 From: jamdavi <73593426+jamdavi@users.noreply.github.com> Date: Fri, 4 Jun 2021 15:33:11 -0400 Subject: [PATCH 12/77] feat(e2e-tests): Add properties E2E tests Co-authored-by: Abhipsa Misra --- e2e/test/Helpers/TestDeviceCallbackHandler.cs | 201 +++++--- .../iothub/properties/PropertiesE2ETests.cs | 448 ++++++++++++++++ .../PropertiesWithComponentsE2ETests.cs | 480 ++++++++++++++++++ iothub/device/src/ClientPropertyCollection.cs | 69 +-- iothub/device/src/PayloadCollection.cs | 12 +- .../tests/ClientPropertyCollectionTests.cs | 38 +- ...ClientPropertyCollectionTestsNewtonsoft.cs | 30 +- 7 files changed, 1141 insertions(+), 137 deletions(-) create mode 100644 e2e/test/iothub/properties/PropertiesE2ETests.cs create mode 100644 e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs diff --git a/e2e/test/Helpers/TestDeviceCallbackHandler.cs b/e2e/test/Helpers/TestDeviceCallbackHandler.cs index e2b075f91b..6f2025de76 100644 --- a/e2e/test/Helpers/TestDeviceCallbackHandler.cs +++ b/e2e/test/Helpers/TestDeviceCallbackHandler.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Linq; using System.Runtime.ExceptionServices; using System.Text; using System.Threading; @@ -29,6 +28,10 @@ public class TestDeviceCallbackHandler : IDisposable private ExceptionDispatchInfo _receiveMessageExceptionDispatch; private Message _expectedMessageSentByService = null; + private readonly SemaphoreSlim _clientPropertyCallbackSemaphore = new SemaphoreSlim(0, 1); + private ExceptionDispatchInfo _clientPropertyExceptionDispatch; + private object _expectedClientPropertyValue = null; + public TestDeviceCallbackHandler(DeviceClient deviceClient, TestDevice testDevice, MsTestLogger logger) { _deviceClient = deviceClient; @@ -48,33 +51,42 @@ public Message ExpectedMessageSentByService set => Volatile.Write(ref _expectedMessageSentByService, value); } - public async Task SetDeviceReceiveMethodAsync(string methodName, string deviceResponseJson, string expectedServiceRequestJson) + public object ExpectedClientPropertyValue { - await _deviceClient.SetMethodHandlerAsync(methodName, - (request, context) => - { - try - { - _logger.Trace($"{nameof(SetDeviceReceiveMethodAsync)}: DeviceClient {_testDevice.Id} callback method: {request.Name} {request.ResponseTimeout}."); - request.Name.Should().Be(methodName, "The expected method name should match what was sent from service"); - request.DataAsJson.Should().Be(expectedServiceRequestJson, "The expected method data should match what was sent from service"); - - return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(deviceResponseJson), 200)); - } - catch (Exception ex) - { - _logger.Trace($"{nameof(SetDeviceReceiveMethodAsync)}: Error during DeviceClient callback method: {ex}."); + get => Volatile.Read(ref _expectedClientPropertyValue); + set => Volatile.Write(ref _expectedClientPropertyValue, value); + } - _methodExceptionDispatch = ExceptionDispatchInfo.Capture(ex); - return Task.FromResult(new MethodResponse(500)); - } - finally + public async Task SetDeviceReceiveMethodAsync(string methodName, string deviceResponseJson, string expectedServiceRequestJson) + { + await _deviceClient + .SetMethodHandlerAsync( + methodName, + (request, context) => { - // Always notify that we got the callback. - _methodCallbackSemaphore.Release(); - } - }, - null).ConfigureAwait(false); + try + { + _logger.Trace($"{nameof(SetDeviceReceiveMethodAsync)}: DeviceClient {_testDevice.Id} callback method: {request.Name} {request.ResponseTimeout}."); + request.Name.Should().Be(methodName, "The expected method name should match what was sent from service"); + request.DataAsJson.Should().Be(expectedServiceRequestJson, "The expected method data should match what was sent from service"); + + return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(deviceResponseJson), 200)); + } + catch (Exception ex) + { + _logger.Trace($"{nameof(SetDeviceReceiveMethodAsync)}: Error during DeviceClient callback method: {ex}."); + + _methodExceptionDispatch = ExceptionDispatchInfo.Capture(ex); + return Task.FromResult(new MethodResponse(500)); + } + finally + { + // Always notify that we got the callback. + _methodCallbackSemaphore.Release(); + } + }, + null) + .ConfigureAwait(false); } public async Task WaitForMethodCallbackAsync(CancellationToken ct) @@ -87,29 +99,32 @@ public async Task SetTwinPropertyUpdateCallbackHandlerAsync(string expectedPropN { string userContext = "myContext"; - await _deviceClient.SetDesiredPropertyUpdateCallbackAsync( - (patch, context) => - { - _logger.Trace($"{nameof(SetTwinPropertyUpdateCallbackHandlerAsync)}: DeviceClient {_testDevice.Id} callback twin: DesiredProperty: {patch}, {context}"); - - try - { - string propertyValue = patch[expectedPropName]; - propertyValue.Should().Be(ExpectedTwinPropertyValue, "The property value should match what was set by service"); - context.Should().Be(userContext, "The context should match what was set by service"); - } - catch (Exception ex) + await _deviceClient + .SetDesiredPropertyUpdateCallbackAsync( + (patch, context) => { - _twinExceptionDispatch = ExceptionDispatchInfo.Capture(ex); - } - finally - { - // Always notify that we got the callback. - _twinCallbackSemaphore.Release(); - } - - return Task.FromResult(true); - }, userContext).ConfigureAwait(false); + _logger.Trace($"{nameof(SetTwinPropertyUpdateCallbackHandlerAsync)}: DeviceClient {_testDevice.Id} callback twin: DesiredProperty: {patch}, {context}"); + + try + { + string propertyValue = patch[expectedPropName]; + propertyValue.Should().Be(ExpectedTwinPropertyValue, "The property value should match what was set by service"); + context.Should().Be(userContext, "The context should match what was set by service"); + } + catch (Exception ex) + { + _twinExceptionDispatch = ExceptionDispatchInfo.Capture(ex); + } + finally + { + // Always notify that we got the callback. + _twinCallbackSemaphore.Release(); + } + + return Task.FromResult(true); + }, + userContext) + .ConfigureAwait(false); } public async Task WaitForTwinCallbackAsync(CancellationToken ct) @@ -120,31 +135,33 @@ public async Task WaitForTwinCallbackAsync(CancellationToken ct) public async Task SetMessageReceiveCallbackHandlerAsync() { - await _deviceClient.SetReceiveMessageHandlerAsync( - async (receivedMessage, context) => - { - _logger.Trace($"{nameof(SetMessageReceiveCallbackHandlerAsync)}: DeviceClient {_testDevice.Id} received message with Id: {receivedMessage.MessageId}."); - - try + await _deviceClient + .SetReceiveMessageHandlerAsync( + async (receivedMessage, context) => { - receivedMessage.MessageId.Should().Be(ExpectedMessageSentByService.MessageId, "Received message Id should match what was sent by service"); - receivedMessage.UserId.Should().Be(ExpectedMessageSentByService.UserId, "Received user Id should match what was sent by service"); - - await CompleteMessageAsync(receivedMessage).ConfigureAwait(false); - _logger.Trace($"{nameof(SetMessageReceiveCallbackHandlerAsync)}: DeviceClient completed message with Id: {receivedMessage.MessageId}."); - } - catch (Exception ex) - { - _logger.Trace($"{nameof(SetMessageReceiveCallbackHandlerAsync)}: Error during DeviceClient receive message callback: {ex}."); - _receiveMessageExceptionDispatch = ExceptionDispatchInfo.Capture(ex); - } - finally - { - // Always notify that we got the callback. - _receivedMessageCallbackSemaphore.Release(); - } - }, - null).ConfigureAwait(false); + _logger.Trace($"{nameof(SetMessageReceiveCallbackHandlerAsync)}: DeviceClient {_testDevice.Id} received message with Id: {receivedMessage.MessageId}."); + + try + { + receivedMessage.MessageId.Should().Be(ExpectedMessageSentByService.MessageId, "Received message Id should match what was sent by service"); + receivedMessage.UserId.Should().Be(ExpectedMessageSentByService.UserId, "Received user Id should match what was sent by service"); + + await CompleteMessageAsync(receivedMessage).ConfigureAwait(false); + _logger.Trace($"{nameof(SetMessageReceiveCallbackHandlerAsync)}: DeviceClient completed message with Id: {receivedMessage.MessageId}."); + } + catch (Exception ex) + { + _logger.Trace($"{nameof(SetMessageReceiveCallbackHandlerAsync)}: Error during DeviceClient receive message callback: {ex}."); + _receiveMessageExceptionDispatch = ExceptionDispatchInfo.Capture(ex); + } + finally + { + // Always notify that we got the callback. + _receivedMessageCallbackSemaphore.Release(); + } + }, + null) + .ConfigureAwait(false); } private async Task CompleteMessageAsync(Client.Message message) @@ -158,6 +175,48 @@ public async Task WaitForReceiveMessageCallbackAsync(CancellationToken ct) _receiveMessageExceptionDispatch?.Throw(); } + public async Task SetClientPropertyUpdateCallbackHandlerAsync(string expectedPropName, string componentName = default) + { + string userContext = "myContext"; + + await _deviceClient + .SubscribeToWritablePropertiesEventAsync( + (patch, context) => + { + _logger.Trace($"{nameof(SetClientPropertyUpdateCallbackHandlerAsync)}: DeviceClient {_testDevice.Id} callback property: WritableProperty: {patch}, {context}"); + + try + { + bool isPropertyPresent = componentName == null + ? patch.TryGetValue(expectedPropName, out T propertyFromCollection) + : patch.TryGetValue(componentName, expectedPropName, out propertyFromCollection); + + isPropertyPresent.Should().BeTrue(); + propertyFromCollection.Should().BeEquivalentTo((T)ExpectedClientPropertyValue); + context.Should().Be(userContext); + } + catch (Exception ex) + { + _clientPropertyExceptionDispatch = ExceptionDispatchInfo.Capture(ex); + } + finally + { + // Always notify that we got the callback. + _clientPropertyCallbackSemaphore.Release(); + } + + return Task.FromResult(true); + }, + userContext) + .ConfigureAwait(false); + } + + public async Task WaitForClientPropertyUpdateCallbcakAsync(CancellationToken ct) + { + await _clientPropertyCallbackSemaphore.WaitAsync(ct).ConfigureAwait(false); + _clientPropertyExceptionDispatch?.Throw(); + } + public void Dispose() { _methodCallbackSemaphore?.Dispose(); diff --git a/e2e/test/iothub/properties/PropertiesE2ETests.cs b/e2e/test/iothub/properties/PropertiesE2ETests.cs new file mode 100644 index 0000000000..0a2da2c3c7 --- /dev/null +++ b/e2e/test/iothub/properties/PropertiesE2ETests.cs @@ -0,0 +1,448 @@ +// 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.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Azure.Devices.Client; +using Microsoft.Azure.Devices.Client.Exceptions; +using Microsoft.Azure.Devices.E2ETests.Helpers; +using Microsoft.Azure.Devices.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.E2ETests.Properties +{ + [TestClass] + [TestCategory("E2E")] + [TestCategory("IoTHub")] + public class PropertiesE2ETests : E2EMsTestBase + { + private readonly string _devicePrefix = $"E2E_{nameof(PropertiesE2ETests)}_"; + + private static readonly RegistryManager s_registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + private static readonly TimeSpan s_maxWaitTimeForCallback = TimeSpan.FromSeconds(30); + + private static readonly Dictionary s_mapOfPropertyValues = new Dictionary + { + { "key1", 123 }, + { "key2", "someString" }, + { "key3", true } + }; + + [LoggedTestMethod] + public async Task Properties_DeviceSetsPropertyAndGetsItBack_Mqtt() + { + await Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSetsPropertyAndGetsItBack_MqttWs() + { + await Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSetsPropertyMapAndGetsItBack_Mqtt() + { + await Properties_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSetsPropertyMapAndGetsItBack_MqttWs() + { + await Properties_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes_Mqtt() + { + await Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes( + Client.TransportType.Mqtt_Tcp_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes_MqttWs() + { + await Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes( + Client.TransportType.Mqtt_WebSocket_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEvent_Mqtt() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_Tcp_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEvent_MqttWs() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_WebSocket_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_Mqtt() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_Tcp_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_MqttWs() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_WebSocket_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_Mqtt() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_MqttWs() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSetsPropertyAndServiceReceivesIt_Mqtt() + { + await Properties_DeviceSetsPropertyAndServiceReceivesItAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSetsPropertyAndServiceReceivesIt_MqttWs() + { + await Properties_DeviceSetsPropertyAndServiceReceivesItAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingIt_Mqtt() + { + await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingIt_MqttWs() + { + await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ClientHandlesRejectionInvalidPropertyName_Mqtt() + { + await Properties_ClientHandlesRejectionInvalidPropertyNameAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ClientHandlesRejectionInvalidPropertyName_MqttWs() + { + await Properties_ClientHandlesRejectionInvalidPropertyNameAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + private async Task Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + await Properties_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString(), Logger).ConfigureAwait(false); + } + + private async Task Properties_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync(Client.TransportType transport) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + await Properties_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, s_mapOfPropertyValues, Logger).ConfigureAwait(false); + } + + public static async Task Properties_DeviceSetsPropertyAndGetsItBackAsync(DeviceClient deviceClient, string deviceId, T propValue, MsTestLogger logger) + { + string propName = Guid.NewGuid().ToString(); + + logger.Trace($"{nameof(Properties_DeviceSetsPropertyAndGetsItBackAsync)}: name={propName}, value={propValue}"); + + var props = new ClientPropertyCollection(); + props.AddRootProperty(propName, propValue); + await deviceClient.UpdateClientPropertiesAsync(props).ConfigureAwait(false); + + // Validate the updated properties from the device-client + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + bool isPropertyPresent = clientProperties.TryGetValue(propName, out T propFromCollection); + isPropertyPresent.Should().BeTrue(); + propFromCollection.Should().BeEquivalentTo(propValue); + + // Validate the updated twin from the service-client + Twin completeTwin = await s_registryManager.GetTwinAsync(deviceId).ConfigureAwait(false); + dynamic actualProp = completeTwin.Properties.Reported[propName]; + + // The value will be retrieved as a TwinCollection, so we'll serialize the value and then compare. + string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); + serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); + } + + public static async Task RegistryManagerUpdateWritablePropertyAsync(string deviceId, string propName, T propValue) + { + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + + var twinPatch = new Twin(); + twinPatch.Properties.Desired[propName] = propValue; + + await registryManager.UpdateTwinAsync(deviceId, twinPatch, "*").ConfigureAwait(false); + await registryManager.CloseAsync().ConfigureAwait(false); + } + + private async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes(Client.TransportType transport, object propValue) + { + string propName = Guid.NewGuid().ToString(); + + Logger.Trace($"{nameof(Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + // Set a callback + await deviceClient. + SubscribeToWritablePropertiesEventAsync( + (patch, context) => + { + Assert.Fail("After having unsubscribed from receiving client property update notifications " + + "this callback should not have been invoked."); + + return Task.FromResult(true); + }, + null) + .ConfigureAwait(false); + + // Unsubscribe + await deviceClient + .SubscribeToWritablePropertiesEventAsync(null, null) + .ConfigureAwait(false); + + await RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, propName, propValue) + .ConfigureAwait(false); + + await deviceClient.CloseAsync().ConfigureAwait(false); + } + + private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync(Client.TransportType transport, T propValue) + { + using var cts = new CancellationTokenSource(s_maxWaitTimeForCallback); + string propName = Guid.NewGuid().ToString(); + + Logger.Trace($"{nameof(Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); + + await testDeviceCallbackHandler.SetClientPropertyUpdateCallbackHandlerAsync(propName).ConfigureAwait(false); + testDeviceCallbackHandler.ExpectedClientPropertyValue = propValue; + + await Task + .WhenAll( + RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, propName, propValue), + testDeviceCallbackHandler.WaitForClientPropertyUpdateCallbcakAsync(cts.Token)) + .ConfigureAwait(false); + + // Validate the updated properties from the device-client + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + bool isPropertyPresent = clientProperties.Writable.TryGetValue(propName, out T propValueFromCollection); + isPropertyPresent.Should().BeTrue(); + propValueFromCollection.Should().BeEquivalentTo(propValue); + + // Validate the updated twin from the service-client + Twin completeTwin = await s_registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + dynamic actualProp = completeTwin.Properties.Desired[propName]; + + // The value will be retrieved as a TwinCollection, so we'll serialize the value and then compare. + string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); + serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); + + await deviceClient.SubscribeToWritablePropertiesEventAsync(null, null).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); + } + + private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync(Client.TransportType transport) + { + string propName = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + var twinPatch = new Twin(); + twinPatch.Properties.Desired[propName] = propValue; + await registryManager.UpdateTwinAsync(testDevice.Id, twinPatch, "*").ConfigureAwait(false); + + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + bool isPropertyPresent = clientProperties.Writable.TryGetValue(propName, out string propFromCollection); + isPropertyPresent.Should().BeTrue(); + propFromCollection.Should().Be(propValue); + + await deviceClient.CloseAsync().ConfigureAwait(false); + await registryManager.CloseAsync().ConfigureAwait(false); + } + + private async Task Properties_DeviceSetsPropertyAndServiceReceivesItAsync(Client.TransportType transport) + { + string propName = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + var patch = new ClientPropertyCollection(); + patch.AddRootProperty(propName, propValue); + await deviceClient.UpdateClientPropertiesAsync(patch).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); + + Twin serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + dynamic actualProp = serviceTwin.Properties.Reported[propName]; + + // The value will be retrieved as a TwinCollection, so we'll serialize the value and then compare. + string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); + serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); + } + + private async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync(Client.TransportType transport) + { + string propName1 = Guid.NewGuid().ToString(); + string propName2 = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + string propEmptyValue = "{}"; + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + // First send a property patch with valid values for both prop1 and prop2. + await deviceClient + .UpdateClientPropertiesAsync( + new ClientPropertyCollection + { + [propName1] = new Dictionary + { + [propName2] = propValue + } + }) + .ConfigureAwait(false); + Twin serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(propName1).Should().BeTrue(); + + TwinCollection prop1Value = serviceTwin.Properties.Reported[propName1]; + prop1Value.Contains(propName2).Should().BeTrue(); + + string prop2Value = prop1Value[propName2]; + prop2Value.Should().Be(propValue); + + // Sending a null value for a property will result in service removing the property from the client's twin representation. + // For the property patch sent here will result in propName2 being removed. + await deviceClient + .UpdateClientPropertiesAsync( + new ClientPropertyCollection + { + [propName1] = new Dictionary + { + [propName2] = null + } + }) + .ConfigureAwait(false); + serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(propName1).Should().BeTrue(); + + string serializedActualProperty = JsonConvert.SerializeObject(serviceTwin.Properties.Reported[propName1]); + serializedActualProperty.Should().Be(propEmptyValue); + + // For the property patch sent here will result in propName1 being removed. + await deviceClient + .UpdateClientPropertiesAsync( + new ClientPropertyCollection + { + [propName1] = null + }) + .ConfigureAwait(false); + serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(propName1).Should().BeFalse(); + } + + private async Task Properties_ClientHandlesRejectionInvalidPropertyNameAsync(Client.TransportType transport) + { + string propName1 = "$" + Guid.NewGuid().ToString(); + string propName2 = Guid.NewGuid().ToString(); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + Func func = async () => + { + await deviceClient + .UpdateClientPropertiesAsync( + new ClientPropertyCollection + { + [propName1] = 123, + [propName2] = "abcd" + }) + .ConfigureAwait(false); + }; + await func.Should().ThrowAsync(); + + Twin serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(propName1).Should().BeFalse(); + serviceTwin.Properties.Reported.Contains(propName2).Should().BeFalse(); + } + } + + internal class CustomClientProperty + { + // The properties in here need to be public otherwise NewtonSoft.Json cannot serialize and deserialize them properly. + public int Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs new file mode 100644 index 0000000000..156c67f9fd --- /dev/null +++ b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs @@ -0,0 +1,480 @@ +// 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.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Azure.Devices.Client; +using Microsoft.Azure.Devices.Client.Exceptions; +using Microsoft.Azure.Devices.E2ETests.Helpers; +using Microsoft.Azure.Devices.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Devices.E2ETests.Properties +{ + [TestClass] + [TestCategory("E2E")] + [TestCategory("IoTHub")] + public class PropertiesWithComponentsE2ETests : E2EMsTestBase + { + public const string ComponentName = "testableComponent"; + + private readonly string _devicePrefix = $"E2E_{nameof(PropertiesWithComponentsE2ETests)}_"; + + private static readonly RegistryManager s_registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + private static readonly TimeSpan s_maxWaitTimeForCallback = TimeSpan.FromSeconds(30); + + private static readonly Dictionary s_mapOfPropertyValues = new Dictionary + { + { "key1", 123 }, + { "key2", "someString" }, + { "key3", true } + }; + + [LoggedTestMethod] + public async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBack_Mqtt() + { + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBack_MqttWs() + { + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBack_Mqtt() + { + await PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBack_MqttWs() + { + await PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes_Mqtt() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes( + Client.TransportType.Mqtt_Tcp_Only, + Guid.NewGuid()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes_MqttWs() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes( + Client.TransportType.Mqtt_WebSocket_Only, + Guid.NewGuid()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEvent_Mqtt() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_Tcp_Only, + Guid.NewGuid()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEvent_MqttWs() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_WebSocket_Only, + Guid.NewGuid()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_Mqtt() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_Tcp_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_MqttWs() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( + Client.TransportType.Mqtt_WebSocket_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_Mqtt() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_MqttWs() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesIt_Mqtt() + { + await PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesItAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesIt_MqttWs() + { + await PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesItAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync_Mqtt() + { + await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync_MqttWs() + { + await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyName_Mqtt() + { + await PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync( + Client.TransportType.Mqtt_Tcp_Only) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyName_MqttWs() + { + await PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync( + Client.TransportType.Mqtt_WebSocket_Only) + .ConfigureAwait(false); + } + + private async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString(), Logger).ConfigureAwait(false); + } + + private async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync(Client.TransportType transport) + { + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, s_mapOfPropertyValues, Logger).ConfigureAwait(false); + } + + public static async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(DeviceClient deviceClient, string deviceId, T propValue, MsTestLogger logger) + { + string propName = Guid.NewGuid().ToString(); + + logger.Trace($"{nameof(PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync)}: name={propName}, value={propValue}"); + + var props = new ClientPropertyCollection(); + props.AddComponentProperty(ComponentName, propName, propValue); + await deviceClient.UpdateClientPropertiesAsync(props).ConfigureAwait(false); + + // Validate the updated properties from the device-client + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + bool isPropertyPresent = clientProperties.TryGetValue(ComponentName, propName, out T propFromCollection); + isPropertyPresent.Should().BeTrue(); + propFromCollection.Should().BeEquivalentTo(propValue); + + // Validate the updated twin from the service-client + Twin completeTwin = await s_registryManager.GetTwinAsync(deviceId).ConfigureAwait(false); + dynamic actualProp = completeTwin.Properties.Reported[ComponentName][propName]; + + // The value will be retrieved as a TwinCollection, so we'll serialize the value and then compare. + string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); + serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); + } + + public static async Task RegistryManagerUpdateWritablePropertyAsync(string deviceId, string componentName, string propName, T propValue) + { + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + + var twinPatch = new Twin(); + var componentProperties = new TwinCollection + { + [ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue, + [propName] = propValue + }; + twinPatch.Properties.Desired[componentName] = componentProperties; + + await registryManager.UpdateTwinAsync(deviceId, twinPatch, "*").ConfigureAwait(false); + await registryManager.CloseAsync().ConfigureAwait(false); + } + + private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes(Client.TransportType transport, T propValue) + { + string propName = Guid.NewGuid().ToString(); + + Logger.Trace($"{nameof(PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + // Set a callback + await deviceClient. + SubscribeToWritablePropertiesEventAsync( + (patch, context) => + { + Assert.Fail("After having unsubscribed from receiving client property update notifications " + + "this callback should not have been invoked."); + + return Task.FromResult(true); + }, + null) + .ConfigureAwait(false); + + // Unsubscribe + await deviceClient + .SubscribeToWritablePropertiesEventAsync(null, null) + .ConfigureAwait(false); + + await RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, ComponentName, propName, propValue) + .ConfigureAwait(false); + + await deviceClient.CloseAsync().ConfigureAwait(false); + } + + private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync(Client.TransportType transport, T propValue) + { + using var cts = new CancellationTokenSource(s_maxWaitTimeForCallback); + string propName = Guid.NewGuid().ToString(); + + Logger.Trace($"{nameof(PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); + + await testDeviceCallbackHandler.SetClientPropertyUpdateCallbackHandlerAsync(propName, ComponentName).ConfigureAwait(false); + testDeviceCallbackHandler.ExpectedClientPropertyValue = propValue; + + await Task + .WhenAll( + RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, ComponentName, propName, propValue), + testDeviceCallbackHandler.WaitForClientPropertyUpdateCallbcakAsync(cts.Token)) + .ConfigureAwait(false); + + // Validate the updated properties from the device-client + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + bool isPropertyPresent = clientProperties.Writable.TryGetValue(ComponentName, propName, out T propFromCollection); + isPropertyPresent.Should().BeTrue(); + propFromCollection.Should().BeEquivalentTo(propValue); + + // Validate the updated twin from the service-client + Twin completeTwin = await s_registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + dynamic actualProp = completeTwin.Properties.Desired[ComponentName][propName]; + + // The value will be retrieved as a TwinCollection, so we'll serialize the value and then compare. + string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); + serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); + + await deviceClient.SubscribeToWritablePropertiesEventAsync(null, null).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); + } + + private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync(Client.TransportType transport) + { + string propName = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + var twinPatch = new Twin(); + var componentProperties = new TwinCollection + { + [ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue, + [propName] = propValue + }; + twinPatch.Properties.Desired[ComponentName] = componentProperties; + await registryManager.UpdateTwinAsync(testDevice.Id, twinPatch, "*").ConfigureAwait(false); + + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + bool isPropertyPresent = clientProperties.Writable.TryGetValue(ComponentName, propName, out string propFromCollection); + isPropertyPresent.Should().BeTrue(); + propFromCollection.Should().Be(propValue); + + await deviceClient.CloseAsync().ConfigureAwait(false); + await registryManager.CloseAsync().ConfigureAwait(false); + } + + private async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesItAsync(Client.TransportType transport) + { + string propName = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + var patch = new ClientPropertyCollection(); + patch.AddComponentProperty(ComponentName, propName, propValue); + await deviceClient.UpdateClientPropertiesAsync(patch).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); + + Twin serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + dynamic actualProp = serviceTwin.Properties.Reported[ComponentName][propName]; + + // The value will be retrieved as a TwinCollection, so we'll serialize the value and then compare. + string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); + serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); + } + + private async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync(Client.TransportType transport) + { + string propName1 = Guid.NewGuid().ToString(); + string propName2 = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + string propEmptyValue = "{}"; + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + // First send a property patch with valid values for both prop1 and prop2. + var propertyPatch1 = new ClientPropertyCollection(); + propertyPatch1.AddComponentProperty( + ComponentName, + propName1, + new Dictionary + { + [propName2] = propValue + }); + await deviceClient.UpdateClientPropertiesAsync(propertyPatch1).ConfigureAwait(false); + + Twin serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(ComponentName).Should().BeTrue(); + + TwinCollection componentPatch1 = serviceTwin.Properties.Reported[ComponentName]; + componentPatch1.Contains(propName1).Should().BeTrue(); + + TwinCollection property1Value = componentPatch1[propName1]; + property1Value.Contains(propName2).Should().BeTrue(); + + string property2Value = property1Value[propName2]; + property2Value.Should().Be(propValue); + + // Sending a null value for a property will result in service removing the property from the client's twin representation. + // For the property patch sent here will result in propName2 being removed. + var propertyPatch2 = new ClientPropertyCollection(); + propertyPatch2.AddComponentProperty( + ComponentName, + propName1, + new Dictionary + { + [propName2] = null + }); + await deviceClient.UpdateClientPropertiesAsync(propertyPatch2).ConfigureAwait(false); + + serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(ComponentName).Should().BeTrue(); + + TwinCollection componentPatch2 = serviceTwin.Properties.Reported[ComponentName]; + componentPatch2.Contains(propName1).Should().BeTrue(); + + string serializedActualProperty = JsonConvert.SerializeObject(componentPatch2[propName1]); + serializedActualProperty.Should().Be(propEmptyValue); + + // For the property patch sent here will result in propName1 being removed. + var propertyPatch3 = new ClientPropertyCollection(); + propertyPatch3.AddComponentProperty(ComponentName, propName1, null); + await deviceClient.UpdateClientPropertiesAsync(propertyPatch3).ConfigureAwait(false); + + serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(ComponentName).Should().BeTrue(); + + // The only elements within the component should be the component identifiers. + TwinCollection componentPatch3 = serviceTwin.Properties.Reported[ComponentName]; + componentPatch3.Count.Should().Be(1); + componentPatch3.Contains(ConventionBasedConstants.ComponentIdentifierKey).Should().BeTrue(); + + // For the property patch sent here will result in the component being removed. + var propertyPatch4 = new ClientPropertyCollection(); + propertyPatch4.AddComponentProperties(ComponentName, null); + await deviceClient.UpdateClientPropertiesAsync(propertyPatch4).ConfigureAwait(false); + + serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(ComponentName).Should().BeFalse(); + } + + private async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync(Client.TransportType transport) + { + string propName1 = "$" + Guid.NewGuid().ToString(); + string propName2 = Guid.NewGuid().ToString(); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + Func func = async () => + { + await deviceClient + .UpdateClientPropertiesAsync( + new ClientPropertyCollection + { + { + ComponentName, + new Dictionary { + { propName1, 123 }, + { propName2, "abcd" } + } + } + }) + .ConfigureAwait(false); + }; + await func.Should().ThrowAsync(); + + Twin serviceTwin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); + serviceTwin.Properties.Reported.Contains(ComponentName).Should().BeFalse(); + } + } + + internal class CustomClientPropertyWithComponent + { + // The properties in here need to be public otherwise NewtonSoft.Json cannot serialize and deserialize them properly. + public int Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 45aef54502..1930f0bb98 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -28,7 +28,7 @@ public class ClientPropertyCollection : PayloadCollection /// /// The name of the property to add. /// The value of the property to add. - /// is null. + /// is null. /// already exists in the collection. public void AddRootProperty(string propertyName, object propertyValue) => AddInternal(new Dictionary { { propertyName, propertyValue } }, null, false); @@ -48,7 +48,6 @@ public void AddComponentProperty(string componentName, string propertyName, obje /// /// - /// /// /// /// Adds the value to the collection. @@ -59,7 +58,6 @@ public void AddComponentProperties(string componentName, IDictionary AddInternal(properties, componentName, true); /// - /// /// /// /// Adds the values to the collection. @@ -79,6 +77,7 @@ public void AddComponentProperties(string componentName, IDictionary /// /// A collection of properties to add. + /// is null. public void Add(IDictionary properties) { if (properties == null) @@ -112,7 +111,6 @@ public void AddOrUpdateComponentProperty(string componentName, string propertyNa /// /// - /// /// /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. /// @@ -127,8 +125,9 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionary AddInternal(properties, componentName, true); /// - /// /// + /// + /// /// /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. /// @@ -232,7 +231,7 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou /// /// Converts a collection to a properties collection. /// - /// This internal class is aware of the implementation of the TwinCollection ad will + /// This internal class is aware of the implementation of the TwinCollection. /// The TwinCollection object to convert. /// A convention handler that defines the content encoding and serializer to use for the payload. /// A new instance of the class from an existing using an optional . @@ -297,18 +296,20 @@ internal static ClientPropertyCollection FromClientTwinDictionary(IDictionaryThe component with the properties to add or update. /// Forces the collection to use the Add or Update behavior. /// Setting to true will simply overwrite the value. Setting to false will use - /// is null. + /// is null for a root-level property operation. private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) { - if (properties == null) - { - throw new ArgumentNullException(nameof(properties)); - } - // If the componentName is null then simply add the key-value pair to Collection dictionary. // This will either insert a property or overwrite it if it already exists. if (componentName == null) { + // If both the component name and properties collection are null then throw a ArgumentNullException. + // This is not a valid use-case. + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + foreach (KeyValuePair entry in properties) { if (forceUpdate) @@ -323,30 +324,38 @@ private void AddInternal(IDictionary properties, string componen } else { - // If the component name already exists within the dictionary, then the value is a dictionary containing the component level property key and values. - // Append this property dictionary to the existing property value dictionary (overwrite entries if they already exist, if forceUpdate is true). - // Otherwise, if the component name does not exist in the dictionary, then add this as a new entry. - var componentProperties = new Dictionary(); - if (Collection.ContainsKey(componentName)) - { - componentProperties = (Dictionary)Collection[componentName]; - } - foreach (KeyValuePair entry in properties) + Dictionary componentProperties = null; + + // If the supplied properties are non-null, then add or update the supplied property dictionary to the collection. + // If the supplied properties are null, then this operation is to remove a component from the client's twin representation. + // It is added to the collection as-is. + if (properties != null) { - if (forceUpdate) + // If the component name already exists within the dictionary, then the value is a dictionary containing the component level property key and values. + // Otherwise, it is added as a new entry. + componentProperties = new Dictionary(); + if (Collection.ContainsKey(componentName)) { - componentProperties[entry.Key] = entry.Value; + componentProperties = (Dictionary)Collection[componentName]; } - else + + foreach (KeyValuePair entry in properties) { - componentProperties.Add(entry.Key, entry.Value); + if (forceUpdate) + { + componentProperties[entry.Key] = entry.Value; + } + else + { + componentProperties.Add(entry.Key, entry.Value); + } } - } - // For a component level property, the property patch needs to contain the {"__t": "c"} component identifier. - if (!componentProperties.ContainsKey(ConventionBasedConstants.ComponentIdentifierKey)) - { - componentProperties[ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue; + // For a component level property, the property patch needs to contain the {"__t": "c"} component identifier. + if (!componentProperties.ContainsKey(ConventionBasedConstants.ComponentIdentifierKey)) + { + componentProperties[ConventionBasedConstants.ComponentIdentifierKey] = ConventionBasedConstants.ComponentIdentifierValue; + } } Collection[componentName] = componentProperties; diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index 2faaca58c0..b4b7ea03ae 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -33,6 +33,14 @@ public abstract class PayloadCollection : IEnumerable /// /// This accessor is best used to access and cast to simple types. /// It is recommended to use to deserialize to a complex type. + /// + /// + /// For setting component-level property values see + /// and instead. + /// These convenience methods ensure that component-level properties include the component identifier markers { "__t": "c" }. + /// For more information see . + /// + /// /// /// Key of value. /// The specified property. @@ -47,7 +55,7 @@ public virtual object this[string key] /// /// /// For property operations see - /// and instead. + /// and instead. /// /// /// @@ -63,7 +71,7 @@ public virtual void Add(string key, object value) /// /// /// For property operations see - /// and instead. + /// and instead. /// /// The name of the telemetry. /// The value of the telemetry. diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index 71c58426c2..d5f3b4ec5f 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -13,11 +13,11 @@ namespace Microsoft.Azure.Devices.Client.Tests [TestCategory("Unit")] public class ClientPropertyCollectionTests { - private const string BoolPropertyName = "boolProperty"; - private const string DoublePropertyName = "doubleProperty"; - private const string FloatPropertyName = "floatProperty"; - private const string IntPropertyName = "intProperty"; - private const string ShortPropertyName = "shortProperty"; + private const string BoolPropertyName = "boolPropertyName"; + private const string DoublePropertyName = "doublePropertyName"; + private const string FloatPropertyName = "floatPropertyName"; + private const string IntPropertyName = "intPropertyName"; + private const string ShortPropertyName = "shortPropertyName"; private const string StringPropertyName = "stringPropertyName"; private const string ObjectPropertyName = "objectPropertyName"; private const string ArrayPropertyName = "arrayPropertyName"; @@ -38,7 +38,7 @@ public class ClientPropertyCollectionTests private static readonly DateTimeOffset s_dateTimePropertyValue = DateTimeOffset.Now; private static readonly CustomClientProperty s_objectPropertyValue = new CustomClientProperty { Id = 123, Name = "testName" }; - private static readonly List s_arrayPropertyValues = new List + private static readonly List s_arrayPropertyValue = new List { 1, "someString", @@ -46,7 +46,7 @@ public class ClientPropertyCollectionTests s_objectPropertyValue }; - private static readonly Dictionary s_mapPropertyValues = new Dictionary + private static readonly Dictionary s_mapPropertyValue = new Dictionary { { "key1", "value1" }, { "key2", 123 }, @@ -65,8 +65,8 @@ public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceC { IntPropertyName, IntPropertyValue }, { ShortPropertyName, ShortPropertyValue }, { ObjectPropertyName, s_objectPropertyValue }, - { ArrayPropertyName, s_arrayPropertyValues }, - { MapPropertyName, s_mapPropertyValues }, + { ArrayPropertyName, s_arrayPropertyValue }, + { MapPropertyName, s_mapPropertyValue }, { DateTimePropertyName, s_dateTimePropertyValue } }; @@ -93,12 +93,12 @@ public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceC objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); clientProperties.TryGetValue(ArrayPropertyName, out List arrayOutValue); - arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); - arrayOutValue.Should().BeEquivalentTo(s_arrayPropertyValues); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValue); + arrayOutValue.Should().BeEquivalentTo(s_arrayPropertyValue); clientProperties.TryGetValue(MapPropertyName, out Dictionary mapOutValue); - mapOutValue.Should().HaveSameCount(s_mapPropertyValues); - mapOutValue.Should().BeEquivalentTo(s_mapPropertyValues); + mapOutValue.Should().HaveSameCount(s_mapPropertyValue); + mapOutValue.Should().BeEquivalentTo(s_mapPropertyValue); clientProperties.TryGetValue(DateTimePropertyName, out DateTimeOffset dateTimeOutValue); dateTimeOutValue.Should().Be(s_dateTimePropertyValue); @@ -173,8 +173,8 @@ public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWi { IntPropertyName, IntPropertyValue }, { ShortPropertyName, ShortPropertyValue }, { ObjectPropertyName, s_objectPropertyValue }, - { ArrayPropertyName, s_arrayPropertyValues }, - { MapPropertyName, s_mapPropertyValues }, + { ArrayPropertyName, s_arrayPropertyValue }, + { MapPropertyName, s_mapPropertyValue }, { DateTimePropertyName, s_dateTimePropertyValue } } } }; @@ -202,12 +202,12 @@ public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWi objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); clientProperties.TryGetValue(ComponentName, ArrayPropertyName, out List arrayOutValue); - arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); - arrayOutValue.Should().BeEquivalentTo(s_arrayPropertyValues); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValue); + arrayOutValue.Should().BeEquivalentTo(s_arrayPropertyValue); clientProperties.TryGetValue(ComponentName, MapPropertyName, out Dictionary mapOutValue); - mapOutValue.Should().HaveSameCount(s_mapPropertyValues); - mapOutValue.Should().BeEquivalentTo(s_mapPropertyValues); + mapOutValue.Should().HaveSameCount(s_mapPropertyValue); + mapOutValue.Should().BeEquivalentTo(s_mapPropertyValue); clientProperties.TryGetValue(ComponentName, DateTimePropertyName, out DateTimeOffset dateTimeOutValue); dateTimeOutValue.Should().Be(s_dateTimePropertyValue); diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index fb7c35bf9d..f212c14107 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -19,11 +19,11 @@ namespace Microsoft.Azure.Devices.Client.Tests // For the purpose of these tests we will create an instance of a Twin class to simulate the service response. public class ClientPropertyCollectionTestsNewtonsoft { - internal const string BoolPropertyName = "boolProperty"; - internal const string DoublePropertyName = "doubleProperty"; - internal const string FloatPropertyName = "floatProperty"; - internal const string IntPropertyName = "intProperty"; - internal const string ShortPropertyName = "shortProperty"; + internal const string BoolPropertyName = "boolPropertyName"; + internal const string DoublePropertyName = "doublePropertyName"; + internal const string FloatPropertyName = "floatPropertyName"; + internal const string IntPropertyName = "intPropertyName"; + internal const string ShortPropertyName = "shortPropertyName"; internal const string StringPropertyName = "stringPropertyName"; internal const string ObjectPropertyName = "objectPropertyName"; internal const string ArrayPropertyName = "arrayPropertyName"; @@ -43,7 +43,7 @@ public class ClientPropertyCollectionTestsNewtonsoft private static readonly DateTimeOffset s_dateTimePropertyValue = DateTimeOffset.Now; private static readonly CustomClientProperty s_objectPropertyValue = new CustomClientProperty { Id = 123, Name = "testName" }; - private static readonly List s_arrayPropertyValues = new List + private static readonly List s_arrayPropertyValue = new List { 1, "someString", @@ -51,7 +51,7 @@ public class ClientPropertyCollectionTestsNewtonsoft s_objectPropertyValue }; - private static readonly Dictionary s_mapPropertyValues = new Dictionary + private static readonly Dictionary s_mapPropertyValue = new Dictionary { { "key1", "value1" }, { "key2", 123 }, @@ -68,8 +68,8 @@ public class ClientPropertyCollectionTestsNewtonsoft ShortProperty = ShortPropertyValue, StringProperty = StringPropertyValue, ObjectProperty = s_objectPropertyValue, - ArrayProperty = s_arrayPropertyValues, - MapProperty = s_mapPropertyValues, + ArrayProperty = s_arrayPropertyValue, + MapProperty = s_mapPropertyValue, DateTimeProperty = s_dateTimePropertyValue }; @@ -86,8 +86,8 @@ public class ClientPropertyCollectionTestsNewtonsoft ShortProperty = ShortPropertyValue, StringProperty = StringPropertyValue, ObjectProperty = s_objectPropertyValue, - ArrayProperty = s_arrayPropertyValues, - MapProperty = s_mapPropertyValues, + ArrayProperty = s_arrayPropertyValue, + MapProperty = s_mapPropertyValue, DateTimeProperty = s_dateTimePropertyValue }, }; @@ -148,12 +148,12 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValue() // The two lists won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the list is deserialized to a JObject. clientProperties.TryGetValue(ArrayPropertyName, out List arrayOutValue); - arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValue); // The two dictionaries won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the dictionary is deserialized to a JObject. clientProperties.TryGetValue(MapPropertyName, out Dictionary mapOutValue); - mapOutValue.Should().HaveSameCount(s_mapPropertyValues); + mapOutValue.Should().HaveSameCount(s_mapPropertyValue); clientProperties.TryGetValue(DateTimePropertyName, out DateTimeOffset dateTimeOutValue); dateTimeOutValue.Should().Be(s_dateTimePropertyValue); @@ -189,12 +189,12 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() // The two lists won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the list is deserialized to a JObject. clientProperties.TryGetValue(ComponentName, ArrayPropertyName, out List arrayOutValue); - arrayOutValue.Should().HaveSameCount(s_arrayPropertyValues); + arrayOutValue.Should().HaveSameCount(s_arrayPropertyValue); // The two dictionaries won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the dictionary is deserialized to a JObject. clientProperties.TryGetValue(ComponentName, MapPropertyName, out Dictionary mapOutValue); - mapOutValue.Should().HaveSameCount(s_mapPropertyValues); + mapOutValue.Should().HaveSameCount(s_mapPropertyValue); clientProperties.TryGetValue(ComponentName, DateTimePropertyName, out DateTimeOffset dateTimeOutValue); dateTimeOutValue.Should().Be(s_dateTimePropertyValue); From 08dfff33f365088d9843e410d2354774f61e4947 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 4 Jun 2021 17:13:59 -0700 Subject: [PATCH 13/77] feat(e2e-tests): Add fault injection tests for properties operations (#2001) --- .../PropertiesFaultInjectionTests.cs | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs diff --git a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs new file mode 100644 index 0000000000..aa32c93a20 --- /dev/null +++ b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs @@ -0,0 +1,223 @@ +// 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.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Azure.Devices.Client; +using Microsoft.Azure.Devices.E2ETests.Helpers; +using Microsoft.Azure.Devices.E2ETests.Helpers.Templates; +using Microsoft.Azure.Devices.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.E2ETests.Properties +{ + [TestClass] + [TestCategory("E2E")] + [TestCategory("IoTHub")] + [TestCategory("FaultInjection")] + public class PropertiesFaultInjectionTests : E2EMsTestBase + { + private static readonly string s_devicePrefix = $"E2E_{nameof(PropertiesFaultInjectionTests)}_"; + + [LoggedTestMethod] + public async Task Properties_DeviceUpdateClientPropertiesTcpConnRecovery_Mqtt() + { + await Properties_DeviceUpdateClientPropertiesRecoveryAsync( + Client.TransportType.Mqtt_Tcp_Only, + FaultInjection.FaultType_Tcp, + FaultInjection.FaultCloseReason_Boom, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceUpdateClientPropertiesTcpConnRecovery_MqttWs() + { + await Properties_DeviceUpdateClientPropertiesRecoveryAsync( + Client.TransportType.Mqtt_WebSocket_Only, + FaultInjection.FaultType_Tcp, + FaultInjection.FaultCloseReason_Boom, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceUpdateClientPropertiesGracefulShutdownRecovery_Mqtt() + { + await Properties_DeviceUpdateClientPropertiesRecoveryAsync( + Client.TransportType.Mqtt_Tcp_Only, + FaultInjection.FaultType_GracefulShutdownMqtt, + FaultInjection.FaultCloseReason_Bye, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceUpdateClientPropertiesGracefulShutdownRecovery_MqttWs() + { + await Properties_DeviceUpdateClientPropertiesRecoveryAsync( + Client.TransportType.Mqtt_WebSocket_Only, + FaultInjection.FaultType_GracefulShutdownMqtt, + FaultInjection.FaultCloseReason_Bye, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceReceivePropertyUpdateTcpConnRecovery_Mqtt() + { + await Properties_DeviceReceivePropertyUpdateRecoveryAsync( + Client.TransportType.Mqtt_Tcp_Only, + FaultInjection.FaultType_Tcp, + FaultInjection.FaultCloseReason_Boom, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceReceivePropertyUpdateTcpConnRecovery_MqttWs() + { + await Properties_DeviceReceivePropertyUpdateRecoveryAsync( + Client.TransportType.Mqtt_WebSocket_Only, + FaultInjection.FaultType_Tcp, + FaultInjection.FaultCloseReason_Boom, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceReceivePropertyUpdateGracefulShutdownRecovery_Mqtt() + { + await Properties_DeviceReceivePropertyUpdateRecoveryAsync( + Client.TransportType.Mqtt_Tcp_Only, + FaultInjection.FaultType_GracefulShutdownMqtt, + FaultInjection.FaultCloseReason_Bye, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_DeviceReceivePropertyUpdateGracefulShutdownRecovery_MqttWs() + { + await Properties_DeviceReceivePropertyUpdateRecoveryAsync( + Client.TransportType.Mqtt_WebSocket_Only, + FaultInjection.FaultType_GracefulShutdownMqtt, + FaultInjection.FaultCloseReason_Bye, + FaultInjection.DefaultFaultDelay) + .ConfigureAwait(false); + } + + private async Task Properties_DeviceUpdateClientPropertiesRecoveryAsync( + Client.TransportType transport, + string faultType, + string reason, + TimeSpan delayInSec, + string proxyAddress = null) + { + static async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice) + { + string propName = Guid.NewGuid().ToString(); + string propValue = Guid.NewGuid().ToString(); + + var properties = new ClientPropertyCollection(); + properties.AddRootProperty(propName, propValue); + + await deviceClient.UpdateClientPropertiesAsync(properties).ConfigureAwait(false); + + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + clientProperties.Should().NotBeNull(); + + bool isPropertyPresent = clientProperties.TryGetValue(propName, out string propFromCollection); + isPropertyPresent.Should().BeTrue(); + propFromCollection.Should().Be(propValue); + } + + await FaultInjection + .TestErrorInjectionAsync( + s_devicePrefix, + TestDeviceType.Sasl, + transport, + proxyAddress, + faultType, + reason, + delayInSec, + FaultInjection.DefaultFaultDuration, + (d, t) => { return Task.FromResult(false); }, + TestOperationAsync, + () => { return Task.FromResult(false); }, + Logger) + .ConfigureAwait(false); + } + + private async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, string propValue) + { + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + + var twinPatch = new Twin(); + twinPatch.Properties.Desired[propName] = propValue; + + await registryManager.UpdateTwinAsync(deviceId, twinPatch, "*").ConfigureAwait(false); + await registryManager.CloseAsync().ConfigureAwait(false); + } + + private async Task Properties_DeviceReceivePropertyUpdateRecoveryAsync( + Client.TransportType transport, + string faultType, + string reason, + TimeSpan delayInSec, + string proxyAddress = null) + { + TestDeviceCallbackHandler testDeviceCallbackHandler = null; + using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); + + string propName = Guid.NewGuid().ToString(); + + // Configure the callback and start accepting property update notifications. + async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) + { + testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); + await testDeviceCallbackHandler.SetClientPropertyUpdateCallbackHandlerAsync(propName).ConfigureAwait(false); + } + + // Change the properties from the service side and verify the device received it. + async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice) + { + string propValue = Guid.NewGuid().ToString(); + testDeviceCallbackHandler.ExpectedClientPropertyValue = propValue; + + Logger.Trace($"{nameof(Properties_DeviceReceivePropertyUpdateRecoveryAsync)}: name={propName}, value={propValue}"); + + await Task + .WhenAll( + RegistryManagerUpdateDesiredPropertyAsync(testDevice.Id, propName, propValue), + testDeviceCallbackHandler.WaitForClientPropertyUpdateCallbcakAsync(cts.Token)) + .ConfigureAwait(false); + } + + // Cleanup references. + Task CleanupOperationAsync() + { + testDeviceCallbackHandler?.Dispose(); + return Task.FromResult(false); + } + + await FaultInjection + .TestErrorInjectionAsync( + s_devicePrefix, + TestDeviceType.Sasl, + transport, + proxyAddress, + faultType, + reason, + delayInSec, + FaultInjection.DefaultFaultDuration, + InitOperationAsync, + TestOperationAsync, + CleanupOperationAsync, + Logger) + .ConfigureAwait(false); + } + } +} From 50ac3b8ee8b0f8cb43685adaa5720d0a7c4c263d Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 4 Jun 2021 17:46:42 -0700 Subject: [PATCH 14/77] fix(iot-device, shared, samples): Rename StatusCodes to CommonClientResponseCodes and add a comment to highlight ClientOptions behavior --- .../devdoc/Convention-based operations.md | 20 ++++++--- .../TemperatureControllerSample.cs | 22 +++++----- .../Thermostat/ThermostatSample.cs | 14 +++--- iothub/device/src/ClientOptions.cs | 1 + .../tests/ClientPropertyCollectionTests.cs | 4 +- ...ClientPropertyCollectionTestsNewtonsoft.cs | 2 +- shared/src/CommonClientResponseCodes.cs | 43 +++++++++++++++++++ shared/src/StatusCodes.cs | 33 -------------- 8 files changed, 79 insertions(+), 60 deletions(-) create mode 100644 shared/src/CommonClientResponseCodes.cs delete mode 100644 shared/src/StatusCodes.cs diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index 0e8d97735a..f917b531bc 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -6,6 +6,14 @@ public class ClientOptions { + public PayloadConvention PayloadConvention { get; set; } } + +public class DeviceClient : IDisposable { ++ public PayloadConvention PayloadConvention { get; } +} + +public class ModuleClient : IDisposable { ++ public PayloadConvention PayloadConvention { get; } +} ``` ```csharp @@ -85,12 +93,12 @@ public static class ConventionBasedConstants { public const string ValuePropertyName = "value"; } -public class StatusCodes { - public StatusCodes(); - public static int Accepted { get; } - public static int BadRequest { get; } - public static int NotFound { get; } - public static int OK { get; } +public class CommonClientResponseCodes { + public const int Accepted = 202; + public const int BadRequest = 400; + public const int NotFound = 404; + public const int OK = 200; + public CommonClientResponseCodes(); } ``` diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs index a4d3ee42be..0e69f43731 100644 --- a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs @@ -132,14 +132,14 @@ private async Task HandleTargetTemperatureUpdateRequestAsync(string componentNam IWritablePropertyResponse writableResponse = _deviceClient .PayloadConvention .PayloadSerializer - .CreateWritablePropertyResponse(_temperature[componentName], StatusCodes.OK, version, "Successfully updated target temperature."); + .CreateWritablePropertyResponse(_temperature[componentName], CommonClientResponseCodes.OK, version, "Successfully updated target temperature."); var reportedProperty = new ClientPropertyCollection(); reportedProperty.AddComponentProperty(componentName, targetTemperatureProperty, writableResponse); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); - _logger.LogDebug($"Property: Update - component=\"{componentName}\", {reportedProperty.GetSerializedString()} is {nameof(StatusCodes.OK)} " + + _logger.LogDebug($"Property: Update - component=\"{componentName}\", {reportedProperty.GetSerializedString()} is {nameof(CommonClientResponseCodes.OK)} " + $"with a version of {updateResponse.Version}."); } @@ -166,7 +166,7 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, _logger.LogWarning($"Received a command request that isn't" + $" implemented - component name = {commandRequest.ComponentName}, command name = {commandRequest.CommandName}"); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } // For the default case, first check if CommandRequest.ComponentName is null. @@ -184,7 +184,7 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, _logger.LogWarning($"Received a command request that isn't" + $" implemented - command name = {commandRequest.CommandName}"); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } } else @@ -192,7 +192,7 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, _logger.LogWarning($"Received a command request that isn't" + $" implemented - component name = {commandRequest.ComponentName}, command name = {commandRequest.CommandName}"); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } } } @@ -214,12 +214,12 @@ private async Task HandleRebootCommandAsync(CommandRequest comm _temperatureReadingsDateTimeOffset.Clear(); _logger.LogDebug($"Command: Reboot completed."); - return new CommandResponse(StatusCodes.OK); + return new CommandResponse(CommonClientResponseCodes.OK); } catch (JsonReaderException ex) { _logger.LogDebug($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); - return new CommandResponse(StatusCodes.BadRequest); + return new CommandResponse(CommonClientResponseCodes.BadRequest); } } @@ -254,25 +254,25 @@ private Task HandleMaxMinReportCommandAsync(CommandRequest comm $" maxTemp={report.MaximumTemperature}, minTemp={report.MinimumTemperature}, avgTemp={report.AverageTemperature}, " + $"startTime={report.StartTime.LocalDateTime}, endTime={report.EndTime.LocalDateTime}"); - return Task.FromResult(new CommandResponse(report, StatusCodes.OK)); + return Task.FromResult(new CommandResponse(report, CommonClientResponseCodes.OK)); } _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\"," + $" no relevant readings found since {sinceInUtc.LocalDateTime}, cannot generate any report."); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\", no temperature readings sent yet," + $" cannot generate any report."); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } catch (JsonReaderException ex) { _logger.LogError($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); - return Task.FromResult(new CommandResponse(StatusCodes.BadRequest)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.BadRequest)); } } diff --git a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs index d59d4bd3ab..f60610b5d9 100644 --- a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs +++ b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs @@ -82,14 +82,14 @@ private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableP IWritablePropertyResponse writableResponse = _deviceClient .PayloadConvention .PayloadSerializer - .CreateWritablePropertyResponse(_temperature, StatusCodes.OK, writableProperties.Version, "Successfully updated target temperature"); + .CreateWritablePropertyResponse(_temperature, CommonClientResponseCodes.OK, writableProperties.Version, "Successfully updated target temperature"); var reportedProperty = new ClientPropertyCollection(); reportedProperty.AddRootProperty(targetTemperatureProperty, writableResponse); ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); - _logger.LogDebug($"Property: Update - {reportedProperty.GetSerializedString()} is {nameof(StatusCodes.OK)} " + + _logger.LogDebug($"Property: Update - {reportedProperty.GetSerializedString()} is {nameof(CommonClientResponseCodes.OK)} " + $"with a version of {updateResponse.Version}."); break; @@ -133,25 +133,25 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, $" maxTemp={report.MaximumTemperature}, minTemp={report.MinimumTemperature}, avgTemp={report.AverageTemperature}, " + $"startTime={report.StartTime.LocalDateTime}, endTime={report.EndTime.LocalDateTime}"); - return Task.FromResult(new CommandResponse(report, StatusCodes.OK)); + return Task.FromResult(new CommandResponse(report, CommonClientResponseCodes.OK)); } _logger.LogDebug($"Command: No relevant readings found since {sinceInUtc.LocalDateTime}, cannot generate any report."); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } catch (JsonReaderException ex) { _logger.LogError($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); - return Task.FromResult(new CommandResponse(StatusCodes.BadRequest)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.BadRequest)); } default: _logger.LogWarning($"Received a command request that isn't" + $" implemented - command name = {commandRequest.CommandName}"); - return Task.FromResult(new CommandResponse(StatusCodes.NotFound)); + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); } } @@ -193,7 +193,7 @@ private async Task UpdateMaxTemperatureSinceLastRebootPropertyAsync() ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties); - _logger.LogDebug($"Property: Update - {reportedProperties.GetSerializedString()} is {nameof(StatusCodes.OK)} " + + _logger.LogDebug($"Property: Update - {reportedProperties.GetSerializedString()} is {nameof(CommonClientResponseCodes.OK)} " + $"with a version of {updateResponse.Version}."); } diff --git a/iothub/device/src/ClientOptions.cs b/iothub/device/src/ClientOptions.cs index d1ea0c7546..0e4fe18039 100644 --- a/iothub/device/src/ClientOptions.cs +++ b/iothub/device/src/ClientOptions.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Devices.Client { /// /// Options that allow configuration of the device or module client instance during initialization. + /// Updating these options after the client has been initialized will not change the behavior of the client. /// public class ClientOptions { diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index d5f3b4ec5f..f9ea6caa2e 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -271,7 +271,7 @@ public void ClientPropertyCollection_CanAddSimpleWritablePropertyAndGetBackWitho { var clientProperties = new ClientPropertyCollection(); - var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, StatusCodes.OK, 2, WritablePropertyDescription); + var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, CommonClientResponseCodes.OK, 2, WritablePropertyDescription); clientProperties.AddRootProperty(StringPropertyName, writableResponse); clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); @@ -286,7 +286,7 @@ public void ClientPropertyCollection_CanAddWritablePropertyWithComponentAndGetBa { var clientProperties = new ClientPropertyCollection(); - var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, StatusCodes.OK, 2, WritablePropertyDescription); + var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, CommonClientResponseCodes.OK, 2, WritablePropertyDescription); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, writableResponse); clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index f212c14107..c7589789a6 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -95,7 +95,7 @@ public class ClientPropertyCollectionTestsNewtonsoft // Create a writable property response with the expected values. private static readonly IWritablePropertyResponse s_writablePropertyResponse = new NewtonsoftJsonWritablePropertyResponse( propertyValue: StringPropertyValue, - ackCode: StatusCodes.OK, + ackCode: CommonClientResponseCodes.OK, ackVersion: 2, ackDescription: "testableWritablePropertyDescription"); diff --git a/shared/src/CommonClientResponseCodes.cs b/shared/src/CommonClientResponseCodes.cs new file mode 100644 index 0000000000..ba09b2ec15 --- /dev/null +++ b/shared/src/CommonClientResponseCodes.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// A list of common status codes to represent the response from the client. + /// + /// + /// These status codes are based on the HTTP status codes listed here . + /// + [SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", + Justification = "To allow customers to extend this class we need to not mark it static.")] + public class CommonClientResponseCodes + { + /// + /// As per HTTP semantics this code indicates that the request has succeeded. + /// + public const int OK = 200; + + /// + /// As per HTTP semantics this code indicates that the request has been + /// accepted for processing, but the processing has not been completed. + /// + public const int Accepted = 202; + + /// + /// As per HTTP semantics this code indicates that the server cannot or + /// will not process the request due to something that is perceived to be a client error + /// (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). + /// + public const int BadRequest = 400; + + /// + /// As per HTTP semantics this code indicates that the origin server did + /// not find a current representation for the target resource or is not + /// willing to disclose that one exists. + /// + public const int NotFound = 404; + } +} diff --git a/shared/src/StatusCodes.cs b/shared/src/StatusCodes.cs deleted file mode 100644 index a07bba5d10..0000000000 --- a/shared/src/StatusCodes.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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.Shared -{ - /// - /// A list of common status codes to represent the response from the client. - /// - /// - /// These status codes are based on the HTTP status codes listed here - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "To allow customers to extend this class we need to not mark it static.")] - public class StatusCodes - { - /// - /// Status code 200. - /// - public static int OK => 200; - /// - /// Status code 202. - /// - public static int Accepted => 202; - /// - /// Status code 400. - /// - public static int BadRequest => 400; - /// - /// Status code 404. - /// - public static int NotFound => 404; - } -} From 63cdbd42b507f43cfccd8a3e8839ea9a6d102c30 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 7 Jun 2021 13:02:31 -0700 Subject: [PATCH 15/77] fix(iot-device): Fix enumerator implementation to return key-value pairs --- .../devdoc/Convention-based operations.md | 4 +- iothub/device/src/PayloadCollection.cs | 6 +- iothub/device/tests/ClientPropertiesTests.cs | 102 ++++++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 iothub/device/tests/ClientPropertiesTests.cs diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index f917b531bc..d78573ed75 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -66,7 +66,7 @@ public class NewtonsoftJsonPayloadSerializer : PayloadSerializer { public override bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue); } -public abstract class PayloadCollection : IEnumerable, IEnumerable { +public abstract class PayloadCollection : IEnumerable, IEnumerable> { protected PayloadCollection(); public IDictionary Collection { get; private set; } public PayloadConvention Convention { get; internal set; } @@ -75,7 +75,7 @@ public abstract class PayloadCollection : IEnumerable, IEnumerable { public virtual void AddOrUpdate(string key, object value); public void ClearCollection(); public bool Contains(string key); - public IEnumerator GetEnumerator(); + public IEnumerator> GetEnumerator(); public virtual byte[] GetPayloadObjectBytes(); public virtual string GetSerializedString(); protected void SetCollection(PayloadCollection payloadCollection); diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index b4b7ea03ae..1943264358 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Devices.Client /// This classes uses the and /// based by default. /// - public abstract class PayloadCollection : IEnumerable + public abstract class PayloadCollection : IEnumerable> { /// /// The underlying collection for the payload. @@ -166,9 +166,9 @@ public void ClearCollection() } /// - public IEnumerator GetEnumerator() + public IEnumerator> GetEnumerator() { - foreach (object property in Collection) + foreach (KeyValuePair property in Collection) { yield return property; } diff --git a/iothub/device/tests/ClientPropertiesTests.cs b/iothub/device/tests/ClientPropertiesTests.cs new file mode 100644 index 0000000000..4dff48991e --- /dev/null +++ b/iothub/device/tests/ClientPropertiesTests.cs @@ -0,0 +1,102 @@ +// 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.Text; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.Client.Tests +{ + [TestClass] + [TestCategory("Unit")] + public class ClientPropertiesTests + { + private const string BoolPropertyName = "boolPropertyName"; + private const string DoublePropertyName = "doublePropertyName"; + private const string FloatPropertyName = "floatPropertyName"; + private const string StringPropertyName = "stringPropertyName"; + private const string ObjectPropertyName = "objectPropertyName"; + private const string MapPropertyName = "mapPropertyName"; + + private const bool BoolPropertyValue = false; + private const double DoublePropertyValue = 1.001; + private const float FloatPropertyValue = 1.2f; + private const string StringPropertyValue = "propertyValue"; + + private const string ComponentName = "testableComponent"; + + private static readonly CustomClientProperty s_objectPropertyValue = new CustomClientProperty { Id = 123, Name = "testName" }; + + private static readonly Dictionary s_mapPropertyValue = new Dictionary + { + { "key1", "value1" }, + { "key2", 123 }, + { "key3", s_objectPropertyValue } + }; + + [TestMethod] + public void ClientPropertyCollection_CanEnumerateClientProperties() + { + // arrange + var deviceReportedProperties = new ClientPropertyCollection(); + deviceReportedProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + deviceReportedProperties.AddRootProperty(ObjectPropertyName, s_objectPropertyValue); + deviceReportedProperties.AddComponentProperty(ComponentName, BoolPropertyName, BoolPropertyValue); + + var serviceUpdateRequestedProperties = new ClientPropertyCollection(); + serviceUpdateRequestedProperties.AddRootProperty(DoublePropertyName, DoublePropertyValue); + serviceUpdateRequestedProperties.AddRootProperty(MapPropertyName, s_mapPropertyValue); + serviceUpdateRequestedProperties.AddComponentProperty(ComponentName, FloatPropertyName, FloatPropertyValue); + + // act + // The test makes a call to the internal constructor. + // The users will retrieve client properties by calling DeviceClient.GetClientProperties()/ ModuleClient.GetClientProperties(). + var clientProperties = new ClientProperties(serviceUpdateRequestedProperties, deviceReportedProperties); + + // assert + // These are the device reported property values. + foreach (var deviceReportedKeyValuePairs in clientProperties) + { + if (deviceReportedKeyValuePairs.Key.Equals(StringPropertyName)) + { + deviceReportedKeyValuePairs.Value.Should().Be(StringPropertyValue); + } + else if (deviceReportedKeyValuePairs.Key.Equals(ObjectPropertyName)) + { + deviceReportedKeyValuePairs.Value.Should().BeEquivalentTo(s_objectPropertyValue); + } + else if (deviceReportedKeyValuePairs.Key.Equals(ComponentName)) + { + deviceReportedKeyValuePairs.Value.Should().BeOfType(typeof(Dictionary)); + var componentDictionary = deviceReportedKeyValuePairs.Value; + + componentDictionary.As>().TryGetValue(BoolPropertyName, out object outValue).Should().BeTrue(); + outValue.Should().Be(BoolPropertyValue); + } + } + + // These are the property values for which service has requested an update. + foreach (var updateRequestedKeyValuePairs in clientProperties.Writable) + { + if (updateRequestedKeyValuePairs.Key.Equals(DoublePropertyName)) + { + updateRequestedKeyValuePairs.Value.Should().Be(DoublePropertyValue); + } + else if (updateRequestedKeyValuePairs.Key.Equals(MapPropertyName)) + { + updateRequestedKeyValuePairs.Value.Should().BeEquivalentTo(s_mapPropertyValue); + } + else if (updateRequestedKeyValuePairs.Key.Equals(ComponentName)) + { + updateRequestedKeyValuePairs.Value.Should().BeOfType(typeof(Dictionary)); + var componentDictionary = updateRequestedKeyValuePairs.Value; + + componentDictionary.As>().TryGetValue(FloatPropertyName, out object outValue).Should().BeTrue(); + outValue.Should().Be(FloatPropertyValue); + } + } + } + } +} From ce9029df0d3e62382fe82decf01ca02541b047f8 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 7 Jun 2021 17:48:18 -0700 Subject: [PATCH 16/77] fix(iot-device): Make ClientOptions.PayloadConvention readonly --- .../device/devdoc/Convention-based operations.md | 3 ++- .../TemperatureController/Program.cs | 6 ++---- iothub/device/src/ClientOptions.cs | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index d78573ed75..1ffe4c3b6d 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -4,7 +4,8 @@ ```diff public class ClientOptions { -+ public PayloadConvention PayloadConvention { get; set; } ++ public ClientOptions(PayloadConvention payloadConvention = null); ++ public PayloadConvention PayloadConvention { get; } } public class DeviceClient : IDisposable { diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs b/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs index f31383627b..e2fcc0ba2e 100644 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs @@ -129,12 +129,10 @@ private static async Task ProvisionDeviceAsync(Paramet // connection status changes. private static DeviceClient InitializeDeviceClient(string deviceConnectionString) { - var options = new ClientOptions + // Specify a custom System.Text.Json based PayloadConvention to be used. + var options = new ClientOptions(SystemTextJsonPayloadConvention.Instance) { ModelId = ModelId, - - // Specify a custom System.Text.Json based PayloadConvention to be used. - PayloadConvention = SystemTextJsonPayloadConvention.Instance, }; DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); diff --git a/iothub/device/src/ClientOptions.cs b/iothub/device/src/ClientOptions.cs index 0e4fe18039..b3791da9ec 100644 --- a/iothub/device/src/ClientOptions.cs +++ b/iothub/device/src/ClientOptions.cs @@ -12,6 +12,19 @@ namespace Microsoft.Azure.Devices.Client /// public class ClientOptions { + /// + /// Initializes an instance of . + /// + /// + /// The payload convention to be used to serialize and encode the messages for convention based methods. + /// The default value is set to which uses the serializer + /// and encoder. + /// + public ClientOptions(PayloadConvention payloadConvention = default) + { + PayloadConvention = payloadConvention ?? DefaultPayloadConvention.Instance; + } + /// /// The DTDL model Id associated with the device or module client instance. /// This feature is currently supported only over MQTT and AMQP. @@ -67,6 +80,6 @@ public class ClientOptions /// and encoder. /// /// - public PayloadConvention PayloadConvention { get; set; } = DefaultPayloadConvention.Instance; + public PayloadConvention PayloadConvention { get; } } } From 63e866665c2a7738145214e1a93ff2483e6963c8 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 9 Jun 2021 16:45:32 -0700 Subject: [PATCH 17/77] fix(shared): Fix from merge conflict --- shared/src/TwinCollection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/src/TwinCollection.cs b/shared/src/TwinCollection.cs index d868d33ed8..c444cacec5 100644 --- a/shared/src/TwinCollection.cs +++ b/shared/src/TwinCollection.cs @@ -156,6 +156,8 @@ public dynamic this[string propertyName] set => TrySetMemberInternal(propertyName, value); } + internal JObject JObject { get; private set; } + /// public override string ToString() { From 59f4a6d1774d13ac3bca305fd3a69424c81cdd62 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 11 Jun 2021 12:09:38 -0700 Subject: [PATCH 18/77] fix(samples): Update the pnp samples readme (#2025) --- .../TemperatureControllerSample.cs | 18 +- .../Thermostat/ThermostatSample.cs | 2 +- .../convention-based-samples/readme.md | 561 +++++++++++++++++- iothub/device/src/ClientPropertyCollection.cs | 4 +- ...ClientPropertyCollectionTestsNewtonsoft.cs | 4 +- 5 files changed, 554 insertions(+), 35 deletions(-) diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs index 0e69f43731..041adeffb3 100644 --- a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs +++ b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs @@ -59,7 +59,7 @@ public async Task PerformOperationsAsync(CancellationToken cancellationToken) // Verify if the device has previously reported the current value for property "serialNumber". // If the expected value has not been previously reported then send device serial number over property update. - // This is a root-level property update call. + // This is a top-level property update call. await SendDeviceSerialNumberPropertyIfNotCurrentAsync(cancellationToken); bool temperatureReset = true; @@ -83,7 +83,7 @@ public async Task PerformOperationsAsync(CancellationToken cancellationToken) await SendTemperatureAsync(Thermostat2, cancellationToken); // Send working set of device memory over telemetry. - // This is a root-level telemetry call. + // This is a top-level telemetry call. await SendDeviceMemoryTelemetryAsync(cancellationToken); temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0; @@ -96,7 +96,7 @@ private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableP { foreach (KeyValuePair writableProperty in writableProperties) { - // The dispatcher key will be either a root-level property name or a component name. + // The dispatcher key will be either a top-level property name or a component name. switch (writableProperty.Key) { case Thermostat1: @@ -148,7 +148,7 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, { // In this approach, we'll first switch through the component name returned and handle each component-level command. // For the "default" case, we'll first check if the component name is null. - // If null, then this would be a root-level command request, so we'll switch through each root-level command. + // If null, then this would be a top-level command request, so we'll switch through each top-level command. // If not null, then this is a component-level command that has not been implemented. // Switch through CommandRequest.ComponentName to handle all component-level commands. @@ -171,10 +171,10 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, // For the default case, first check if CommandRequest.ComponentName is null. default: - // If CommandRequest.ComponentName is null, then this is a root-level command request. + // If CommandRequest.ComponentName is null, then this is a top-level command request. if (commandRequest.ComponentName == null) { - // Switch through CommandRequest.CommandName to handle all root-level commands. + // Switch through CommandRequest.CommandName to handle all top-level commands. switch (commandRequest.CommandName) { case "reboot": @@ -197,7 +197,7 @@ private Task HandleCommandsAsync(CommandRequest commandRequest, } } - // The callback to handle root-level "reboot" command. + // The callback to handle top-level "reboot" command. // This method will send a temperature update (of 0°C) over telemetry for both associated components. private async Task HandleRebootCommandAsync(CommandRequest commandRequest, object userContext) { @@ -302,7 +302,7 @@ private async Task UpdateDeviceInformationPropertyAsync(CancellationToken cancel } // Send working set of device memory over telemetry. - // This is a root-level telemetry call. + // This is a top-level telemetry call. private async Task SendDeviceMemoryTelemetryAsync(CancellationToken cancellationToken) { const string workingSetName = "workingSet"; @@ -319,7 +319,7 @@ private async Task SendDeviceMemoryTelemetryAsync(CancellationToken cancellation // Verify if the device has previously reported the current value for property "serialNumber". // If the expected value has not been previously reported then send device serial number over property update. - // This is a root-level property update call. + // This is a top-level property update call. private async Task SendDeviceSerialNumberPropertyIfNotCurrentAsync(CancellationToken cancellationToken) { const string serialNumber = "serialNumber"; diff --git a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs index f60610b5d9..d4977479d0 100644 --- a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs +++ b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs @@ -104,7 +104,7 @@ private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableP // The callback to handle command invocation requests. private Task HandleCommandsAsync(CommandRequest commandRequest, object userContext) { - // In this approach, we'll switch through the command name returned and handle each root-level command. + // In this approach, we'll switch through the command name returned and handle each top-level command. switch (commandRequest.CommandName) { case "getMaxMinReport": diff --git a/iothub/device/samples/convention-based-samples/readme.md b/iothub/device/samples/convention-based-samples/readme.md index 578c1b72c6..de7263a860 100644 --- a/iothub/device/samples/convention-based-samples/readme.md +++ b/iothub/device/samples/convention-based-samples/readme.md @@ -12,41 +12,557 @@ products: urlFragment: azure-iot-pnp-device-samples-for-csharp-net --- -# IoT Plug And Play device samples +# IoT Plug And Play (PnP) device/module APIs -These samples demonstrate how a device that follows the [IoT Plug and Play conventions][pnp-convention] interacts with IoT Hub or IoT Central, to: +Device(s)/module(s) connecting to IoT Hub that announce their DTDL model ID during initialization can now perform convention-based operations. One such convention supported is [IoT Plug and Play][pnp-convention]. -- Send telemetry. -- Update read-only and read-write properties. -- Respond to command invocation. +These devices/modules can now use the native PnP APIs in the Azure IoT device SDKs to directly exchange messages with an IoT Hub, without having to manually format these messages to follow the PnP convention. -The samples demonstrate two scenarios: +## Table of Contents -- An IoT Plug and Play device that implements the [Thermostat][d-thermostat] model. This model has a single interface that defines telemetry, read-only and read-write properties, and commands. -- An IoT Plug and Play device that implements the [Temperature controller][d-temperature-controller] model. This model uses multiple components: - - The top-level interface defines telemetry, read-only property and commands. - - The model includes two [Thermostat][thermostat-model] components, and a [device information][d-device-info] component. +- [Client initialization](#client-initialization) + - [Announce model ID during client initialization (same as in latest `master` release)](#announce-model-ID-during-client-initialization-same-as-in-latest-master-release) + - [Define the serialization and encoding convention that the client follows (newly introduced in `preview`)](#define-the-serialization-and-encoding-convention-that-the-client-follows-newly-introduced-in-preview) +- [Terms used](#terms-used) +- [Comparison of API calls - non-convention-aware APIs (old) vs convention-aware APIs (new):](#comparison-of-api-calls---non-convention-aware-apis-old-vs-convention-aware-apis-new) + - [Telemetry](#telemetry) + - [Commands](#commands) + - [Properties](#properties) +- [IoT Plug And Play device samples](#iot-plug-and-play-device-samples) + +## Client initialization + +### Announce model ID during client initialization (same as in latest [`master`][latest-master-release] release) + +```csharp +var options = new ClientOptions +{ + ModelId = ModelId, +}; + +DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); +``` + +### Define the serialization and encoding convention that the client follows (newly introduced in [`preview`][latest-preview-release]) + +```csharp +// Specify a custom System.Text.Json serialization and Utf8 encoding based PayloadConvention to be used. +// If not specified, the library defaults to a convention that uses Newtonsoft.Json-based serializer and Utf8-based encoder. +var options = new ClientOptions(SystemTextJsonPayloadConvention.Instance) +{ + ModelId = ModelId, +}; + +DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); +``` + +## Terms used: +Telemetry, commands, properties and components can all be defined in the contents section of the main interface of a DTDL v2 model. Components enable interfaces to be composed of other interfaces. + +In DTDL v2, a component cannot contain another component. The maximum depth of components is 1. + +- Top-level telemetry/commands/properties + - These refer to the telemetry, commands and properties that are defined directly in the contents section of the main interface of a DTDL v2 model. In case of a model with no components, the main interface refers to the default component. + - When working with this category of telemetry, commands and properties, you do not need to specify any component name. +- Component-level telemetry/commands/properties + - These refer to the telemetry, commands and properties that are defined in the contents section of an interface, which itself is defined as a component within the main interface. + - When working with this category of telemetry, commands and properties, you need to specify the name of the component that these contents belong to. + +## Comparison of API calls - non-convention-aware APIs (old) vs convention-aware APIs (new): + +The following section provides a comparison between the older non-convention-aware APIs (as per latest [`master`][latest-master-release] release) and the newly introduced convention-aware APIs (as per latest [`preview`][latest-preview-release] release). + +## Telemetry + +### Send top-level telemetry: + +#### Using non-convention-aware API (old): + +```csharp +// Send telemetry "serialNumber". +string serialNumber = "SR-1234"; +var telemetry = new Dictionary +{ + ["serialNumber"] = serialNumber, +}; + +using var message = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(telemetry))) +{ + MessageId = s_random.Next().ToString(), + ContentEncoding = "utf-8", + ContentType = "application/json", +}; +await _deviceClient.SendEventAsync(message, cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Send telemetry "serialNumber". +string serialNumber = "SR-1234"; +using var telemetryMessage = new TelemetryMessage +{ + MessageId = Guid.NewGuid().ToString(), + Telemetry = { ["serialNumber"] = serialNumber }, +}; + +await _deviceClient.SendTelemetryAsync(telemetryMessage, cancellationToken); +``` + +### Send component-level telemetry: + +#### Using non-convention-aware API (old): + +```csharp +// Send telemetry "serialNumber" under component "thermostat1". +string serialNumber = "SR-1234"; +var telemetry = new Dictionary() +{ + ["serialNumber"] = serialNumber, +}; + +using var message = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(telemetry))) +{ + MessageId = s_random.Next().ToString(), + ContentEncoding = "utf-8", + ContentType = "application/json", + ComponentName = "thermostat1", +}; +await _deviceClient.SendEventAsync(message, cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Send telemetry "serialNumber" under component "thermostat1". +string serialNumber = "SR-1234"; +using var telemtryMessage = new TelemetryMessage("thermostat1") +{ + MessageId = Guid.NewGuid().ToString(), + Telemetry = { ["serialNumber"] = serialNumber }, +}; + +await _deviceClient.SendTelemetryAsync(telemtryMessage, cancellationToken); +``` + +## Commands + +### Respond to top-level commands: + +#### Using non-convention-aware API (old): + +```csharp +// Subscribe and respond to command "reboot". +await _deviceClient.SetMethodHandlerAsync( + "reboot", + async (methodRequest, userContext) => + { + try + { + int delay = JsonConvert.DeserializeObject(methodRequest.DataAsJson); + await Task.Delay(TimeSpan.FromSeconds(delay)); + + // Application code ... + + return new MethodResponse(CommonClientResponseCodes.OK); + } + catch (JsonReaderException) + { + return new MethodResponse(CommonClientResponseCodes.BadRequest); + } + }, + null, + cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Subscribe and respond to command "reboot". +await _deviceClient.SubscribeToCommandsAsync( + async (commandRequest, userContext) => + { + // This API does not support setting command-level callbacks. + // For this reason we'll need to inspect the commandRequest.CommandName for the request command and perform the actions accordingly. + // Refer to the ThermostatSample.cs for a complete sample implementation. + + if (commandRequest.CommandName == "reboot") + { + try + { + int delay = commandRequest.GetData(); + await Task.Delay(delay * 1000); + + // Application code ... + + return new CommandResponse(CommonClientResponseCodes.OK); + } + catch (JsonReaderException) + { + return new CommandResponse(CommonClientResponseCodes.BadRequest); + } + } + else + { + return new CommandResponse(CommonClientResponseCodes.NotFound); + } + }, + null, + cancellationToken); +``` + +### Respond to component-level commands: + +#### Using non-convention-aware API (old): + +```csharp +// Subscribe and respond to command "getMaxMinReport" under component "thermostat1". +// The method that the application subscribes to is in the format {componentName}*{commandName}. +await _deviceClient.SetMethodHandlerAsync( + "thermostat1*getMaxMinReport", + (commandRequest, userContext) => + { + try + { + DateTimeOffset sinceInUtc = JsonConvert.DeserializeObject(commandRequest.DataAsJson); + + // Application code ... + Report report = GetMaxMinReport(sinceInUtc); + + return Task.FromResult( + new MethodResponse( + Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report)), + CommonClientResponseCodes.OK)); + } + catch (JsonReaderException) + { + return Task.FromResult(new MethodResponse(CommonClientResponseCodes.BadRequest)); + } + }, + null, + cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Subscribe and respond to command "getMaxMinReport" under component "thermostat1". +await _deviceClient.SubscribeToCommandsAsync( + (commandRequest, userContext) => + { + // This API does not support setting command-level callbacks. + // For this reason we'll need to inspect both commandRequest.ComponentName and commandRequest.CommandName, and perform the actions accordingly. + // Refer to the TemperatureControllerSample.cs for a complete sample implementation. + + if (commandRequest.ComponentName == "thermostat1" + && commandRequest.CommandName == "getMaxMinReport") + { + try + { + DateTimeOffset sinceInUtc = commandRequest.GetData(); + + // Application code ... + Report report = GetMaxMinReport(sinceInUtc); + + return Task.FromResult(new CommandResponse(report, CommonClientResponseCodes.OK)); + } + catch (JsonReaderException) + { + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.BadRequest)); + } + } + else + { + return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); + } + } +); +``` -## Configuring the samples in Visual Studio +## Properties -These samples use the `launchSettings.json` in Visual Studio for different configuration settings, one for direct connection strings and one for the Device Provisioning Service (DPS). +### Retrive top-level client properties: -The configuration file is committed to the repository as `launchSettings.template.json`. Rename the file to `launchSettings.json` and then configure it from the **Debug** tab in the project properties. +#### Using non-convention-aware API (old): -## Configuring the samples in VSCode +```csharp +// Retrieve the client's properties. +Twin properties = await _deviceClient.GetTwinAsync(cancellationToken); -These samples use the `launch.json` in Visual Studio Code for different configuration settings, one for direct connection strings and one for DPS. +// To fetch the value of client reported property "serialNumber". +bool isSerialNumberReported = properties.Properties.Reported.Contains("serialNumber"); +if (isSerialNumberReported) +{ + string serialNumberReported = properties.Properties.Reported["serialNumber"]; +} -The configuration file is committed to the repository as `launch.template.json`. Rename it to `launch.json` to take effect when you start a debugging session. +// To fetch the value of service requested "targetTemperature" value. +bool isTargetTemperatureUpdateRequested = properties.Properties.Desired.Contains("targetTemperature"); +if (isTargetTemperatureUpdateRequested) +{ + double targetTemperatureUpdateRequest = properties.Properties.Desired["targetTemperature"]; +} +``` -## Quickstarts and tutorials +#### Using convention-aware API (new): -To learn more about how to configure and run the Thermostat device sample with IoT Hub, see [Quickstart: Connect a sample IoT Plug and Play device application running on Linux or Windows to IoT Hub][thermostat-hub-qs]. +```csharp +// Retrieve the client's properties. + ClientProperties properties = await _deviceClient.GetClientPropertiesAsync(cancellationToken); -To learn more about how to configure and run the Temperature Controller device sample with: +// To fetch the value of client reported property "serialNumber". +bool isSerialNumberReported = properties.TryGetValue("serialNumber", out string serialNumberReported); + + +// To fetch the value of service requested "targetTemperature" value. +bool isTargetTemperatureUpdateRequested = properties.Writable.TryGetValue("targetTemperature", out double targetTemperatureUpdateRequest); +``` + +### Retrive component-level client properties: + +#### Using non-convention-aware API (old): + +```csharp +// Retrieve the client's properties. +Twin properties = await _deviceClient.GetTwinAsync(cancellationToken); + +// To fetch the value of client reported property "serialNumber" under component "thermostat1". +JToken serialNumberJToken = null; +bool isSerialNumberReported = properties.Properties.Reported.Contains("thermostat1") + && ((JObject)properties.Properties.Reported["thermostat1"]).TryGetValue("serialNumber", out serialNumberJToken); + +if (isSerialNumberReported) +{ + string serialNumberReported = serialNumberJToken?.ToObject(); +} + +// To fetch the value of service requested "targetTemperature" value under component "thermostat1". +JToken targetTemperatureUpdateRequestJToken = null; +bool isTargetTemperatureUpdateRequested = properties.Properties.Desired.Contains("thermostat1") + && ((JObject)properties.Properties.Desired["thermostat1"]).TryGetValue("targetTemperature", out targetTemperatureUpdateRequestJToken); + +if (isTargetTemperatureUpdateRequested) +{ + double targetTemperatureUpdateRequest = (double)(targetTemperatureUpdateRequestJToken?.ToObject()); +} +``` + +#### Using convention-aware API (new): + +```csharp +// Retrieve the client's properties. + ClientProperties properties = await _deviceClient.GetClientPropertiesAsync(cancellationToken); + +// To fetch the value of client reported property "serialNumber" under component "thermostat1". +bool isSerialNumberReported = properties.TryGetValue("thermostat1", "serialNumber", out string serialNumberReported); + + +// To fetch the value of service requested "targetTemperature" value under component "thermostat1". +bool isTargetTemperatureUpdateRequested = properties.Writable.TryGetValue("thermostat1", "targetTemperature", out double targetTemperatureUpdateRequest); +``` + +### Update top-level property: + +#### Using non-convention-aware API (old): + +```csharp +// Update the property "serialNumber". +var propertiesToBeUpdated = new TwinCollection +{ + ["serialNumber"] = "SR-1234", +}; +await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Update the property "serialNumber". +var propertiesToBeUpdated = new ClientPropertyCollection +{ + ["serialNumber"] = "SR-1234", +}; +ClientPropertiesUpdateResponse updateResponse = await _deviceClient + .UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); +long updatedVersion = updateResponse.Version; +``` + +### Update component-level properties: + +#### Using non-convention-aware API (old): + +```csharp +// Update the property "serialNumber" under component "thermostat1". +// When calling the UpdateReportedPropertiesAsync API the component-level property update requests must +// include the {"__t": "c"} marker to indicate that the element refers to a component. +var thermostatProperties = new TwinCollection +{ + ["__t"] = "c", + ["serialNumber"] = "SR-1234", +}; +var propertiesToBeUpdated = new TwinCollection +{ + ["thermostat1"] = thermostatProperties +}; +await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Update the property "serialNumber" under component "thermostat1". +var propertiesToBeUpdated = new ClientPropertyCollection(); +propertiesToBeUpdated.AddComponentProperty("thermostat1", "serialNumber", "SR-1234"); + +ClientPropertiesUpdateResponse updateResponse = await _deviceClient + .UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); +long updatedVersion = updateResponse.Version; +``` + +### Respond to top-level property update requests: + +#### Using non-convention-aware API (old): + +```csharp +// Subscribe and respond to event for writable property "targetTemperature". +// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. +await _deviceClient.SetDesiredPropertyUpdateCallbackAsync( + async (desired, userContext) => + { + if (desired.Contains("targetTemperature")) + { + double targetTemperature = desired["targetTemperature"]; + + var targetTemperatureUpdateResponse = new TwinCollection + { + ["value"] = targetTemperature, + ["ac"] = CommonClientResponseCodes.OK, + ["av"] = desired.Version, + ["ad"] = "The operation completed successfully." + }; + var propertiesToBeUpdated = new TwinCollection() + { + ["targetTemperature"] = targetTemperatureUpdateResponse, + }; + + await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); + } + }, + null, + cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Subscribe and respond to event for writable property "targetTemperature". +// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. +await _deviceClient.SubscribeToWritablePropertiesEventAsync( + async (writableProperties, userContext) => + { + if (writableProperties.TryGetValue("targetTemperature", out double targetTemperature)) + { + IWritablePropertyResponse writableResponse = _deviceClient + .PayloadConvention + .PayloadSerializer + .CreateWritablePropertyResponse(targetTemperature, CommonClientResponseCodes.OK, writableProperties.Version, "The operation completed successfully."); + + var propertiesToBeUpdated = new ClientPropertyCollection(); + propertiesToBeUpdated.AddRootProperty("targetTemperature", writableResponse); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); + } + }, + null, + cancellationToken); +``` + +### Respond to component-level property update requests: + +#### Using non-convention-aware API (old): + +```csharp +// Subscribe and respond to event for writable property "targetTemperature" +// under component "thermostat1". +// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. +// When calling the UpdateReportedPropertiesAsync API the component-level property update requests must +// include the {"__t": "c"} marker to indicate that the element refers to a component. +await _deviceClient.SetDesiredPropertyUpdateCallbackAsync( + async (desired, userContext) => + { + if (desired.Contains("thermostat1") + && ((JObject)desired["thermostat1"]) + .TryGetValue("targetTemperature", out JToken targetTemperatureRequested)) + { + double targetTemperature = targetTemperatureRequested + .ToObject(); + + var targetTemperatureUpdateResponse = new TwinCollection + { + ["value"] = targetTemperature, + ["ac"] = CommonClientResponseCodes.OK, + ["av"] = desired.Version, + ["ad"] = "The operation completed successfully." + }; + var thermostatProperties = new TwinCollection() + { + ["__t"] = "c", + ["targetTemperature"] = targetTemperatureUpdateResponse, + }; + var propertiesToBeUpdated = new TwinCollection() + { + ["thermostat1"] = thermostatProperties, + }; + + await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); + } + }, + null, + cancellationToken); +``` + +#### Using convention-aware API (new): + +```csharp +// Subscribe and respond to event for writable property "targetTemperature" +// under component "thermostat1". +// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. +await _deviceClient.SubscribeToWritablePropertiesEventAsync( + async (writableProperties, userContext) => + { + if (writableProperties.TryGetValue("thermostat1", "targetTemperature", out double targetTemperature)) + { + IWritablePropertyResponse writableResponse = _deviceClient + .PayloadConvention + .PayloadSerializer + .CreateWritablePropertyResponse(targetTemperature, CommonClientResponseCodes.OK, writableProperties.Version, "The operation completed successfully."); + + var propertiesToBeUpdated = new ClientPropertyCollection(); + propertiesToBeUpdated.AddComponentProperty("thermostat1", "targetTemperature", writableResponse); + + ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); + } + }, + null, + cancellationToken); +``` + +# IoT Plug And Play device samples + +These samples demonstrate how a device that follows the [IoT Plug and Play conventions][pnp-convention] interacts with IoT Hub or IoT Central, to: + +- Send telemetry. +- Update client properties and be notified of service requested property update requests. +- Respond to command invocation. + +The samples demonstrate two scenarios: + +- An IoT Plug and Play device that implements the [Thermostat][d-thermostat] model. This model has a single interface (the default component) that defines telemetry, properties and commands. +- An IoT Plug and Play device that implements the [Temperature controller][d-temperature-controller] model. This model defines multiple interfaces: + - The top-level interface defines telemetry, properties and commands. + - The model includes two [Thermostat][thermostat-model] components, and a [device information][d-device-info] component. -- IoT Hub, see [Tutorial: Connect an IoT Plug and Play multiple component device application running on Linux or Windows to IoT Hub][temp-controller-hub-tutorial] -- IoT Central, see [Tutorial: Create and connect a client application to your Azure IoT Central application][temp-controller-central-tutorial] +> NOTE: These samples are only meant to demonstrate the usage of Plug and Play APIs. If you are looking for a good device sample to get started with, please see the [device reconnection sample][device-reconnection-sample]. It shows how to connect a device, handle disconnect events, cases to handle when making calls, and when to re-initialize the `DeviceClient`. [pnp-convention]: https://docs.microsoft.com/azure/iot-pnp/concepts-convention [d-thermostat]: ./Thermostat @@ -56,3 +572,6 @@ To learn more about how to configure and run the Temperature Controller device s [thermostat-hub-qs]: https://docs.microsoft.com/azure/iot-pnp/quickstart-connect-device?pivots=programming-language-csharp [temp-controller-hub-tutorial]: https://docs.microsoft.com/azure/iot-pnp/tutorial-multiple-components?pivots=programming-language-csharp [temp-controller-central-tutorial]: https://docs.microsoft.com/azure/iot-central/core/tutorial-connect-device?pivots=programming-language-csharp +[device-reconnection-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/DeviceReconnectionSample +[latest-master-release]: https://github.com/Azure/azure-iot-sdk-csharp/tree/2021-05-13 +[latest-preview-release]: https://github.com/Azure/azure-iot-sdk-csharp/tree/preview_2021-6-8 \ No newline at end of file diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 1930f0bb98..00e2999b9b 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -177,7 +177,7 @@ public bool Contains(string componentName, string propertyName) /// Gets the value of a component-level property. /// /// - /// To get the value of a root-level property use . + /// To get the value of a top-level property use . /// /// The type to cast the object to. /// The component which holds the required property. @@ -296,7 +296,7 @@ internal static ClientPropertyCollection FromClientTwinDictionary(IDictionaryThe component with the properties to add or update. /// Forces the collection to use the Add or Update behavior. /// Setting to true will simply overwrite the value. Setting to false will use - /// is null for a root-level property operation. + /// is null for a top-level property operation. private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) { // If the componentName is null then simply add the key-value pair to Collection dictionary. diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index c7589789a6..07a820febe 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -58,7 +58,7 @@ public class ClientPropertyCollectionTestsNewtonsoft { "key3", s_objectPropertyValue } }; - // Create an object that represents all of the properties as root-level properties. + // Create an object that represents all of the properties as top-level properties. private static readonly RootLevelProperties s_rootLevelProperties = new RootLevelProperties { BooleanProperty = BoolPropertyValue, @@ -99,7 +99,7 @@ public class ClientPropertyCollectionTestsNewtonsoft ackVersion: 2, ackDescription: "testableWritablePropertyDescription"); - // Create a JObject instance that represents a writable property response sent for a root-level property. + // Create a JObject instance that represents a writable property response sent for a top-level property. private static readonly JObject s_writablePropertyResponseJObject = new JObject( new JProperty(StringPropertyName, JObject.FromObject(s_writablePropertyResponse))); From 75034e5b3139de7e58b927b036cdc9b6c6fe2115 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Fri, 18 Jun 2021 09:25:38 -0700 Subject: [PATCH 19/77] Rename E2ETests.Configuration to TestConfiguration to avoid SDK type name conflict --- e2e/test/Helpers/StorageContainer.cs | 2 +- e2e/test/Helpers/TestDevice.cs | 14 ++++---- e2e/test/Helpers/TestModule.cs | 4 +-- ....AzureSecurityCenterForIoTLogAnalytics.cs} | 2 +- ....IoTHub.cs => TestConfiguration.IoTHub.cs} | 2 +- ...g.cs => TestConfiguration.Provisioning.cs} | 2 +- ...torage.cs => TestConfiguration.Storage.cs} | 4 +-- ...{Configuration.cs => TestConfiguration.cs} | 2 +- .../CombinedClientOperationsPoolAmqpTests.cs | 2 +- .../ConnectionStatusChangeHandlerTests.cs | 6 ++-- .../DeviceClientX509AuthenticationE2ETests.cs | 24 ++++++------- e2e/test/iothub/DeviceTokenRefreshE2ETests.cs | 6 ++-- .../iothub/FaultInjectionPoolAmqpTests.cs | 2 +- e2e/test/iothub/FileUploadE2ETests.cs | 8 ++--- .../SasCredentialAuthenticationTests.cs | 28 +++++++-------- .../TokenCredentialAuthenticationTests.cs | 16 ++++----- ...eSecurityCenterForIoTLogAnalyticsClient.cs | 8 ++--- ...ssageReceiveFaultInjectionPoolAmqpTests.cs | 4 +-- .../messaging/MessageFeedbackE2ETests.cs | 2 +- .../MessageReceiveE2EPoolAmqpTests.cs | 6 ++-- .../messaging/MessageReceiveE2ETests.cs | 14 ++++---- .../MessageReceiveFaultInjectionTests.cs | 4 +-- .../iothub/messaging/MessageSendE2ETests.cs | 2 +- .../MessageSendFaultInjectionTests.cs | 2 +- e2e/test/iothub/method/MethodE2ETests.cs | 22 ++++++------ .../method/MethodFaultInjectionTests.cs | 2 +- .../iothub/service/BulkOperationsE2ETests.cs | 8 ++--- .../service/DigitalTwinClientE2ETests.cs | 2 +- .../IoTHubCertificateValidationE2ETest.cs | 8 ++--- .../service/IoTHubServiceProxyE2ETests.cs | 4 +-- e2e/test/iothub/service/PnpServiceTests.cs | 10 +++--- .../iothub/service/RegistryManagerE2ETests.cs | 22 ++++++------ .../RegistryManagerExportDevicesTests.cs | 4 +-- .../RegistryManagerImportDevicesTests.cs | 4 +-- .../iothub/service/ServiceClientE2ETests.cs | 10 +++--- e2e/test/iothub/twin/TwinE2ETests.cs | 12 +++---- .../iothub/twin/TwinFaultInjectionTests.cs | 4 +-- ...rovisioningCertificateValidationE2ETest.cs | 6 ++-- e2e/test/provisioning/ProvisioningE2ETests.cs | 36 +++++++++---------- .../ProvisioningServiceClientE2ETests.cs | 10 +++--- .../provisioning/ReprovisioningE2ETests.cs | 30 ++++++++-------- 41 files changed, 180 insertions(+), 180 deletions(-) rename e2e/test/config/{Configuration.AzureSecurityCenterForIoTLogAnalytics.cs => TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs} (94%) rename e2e/test/config/{Configuration.IoTHub.cs => TestConfiguration.IoTHub.cs} (99%) rename e2e/test/config/{Configuration.Provisioning.cs => TestConfiguration.Provisioning.cs} (97%) rename e2e/test/config/{Configuration.Storage.cs => TestConfiguration.Storage.cs} (85%) rename e2e/test/config/{Configuration.cs => TestConfiguration.cs} (98%) diff --git a/e2e/test/Helpers/StorageContainer.cs b/e2e/test/Helpers/StorageContainer.cs index 94bb4cfcfe..ac450b81cc 100644 --- a/e2e/test/Helpers/StorageContainer.cs +++ b/e2e/test/Helpers/StorageContainer.cs @@ -136,7 +136,7 @@ protected virtual void Dispose(bool disposing) private async Task InitializeAsync() { - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Configuration.Storage.ConnectionString); + CloudStorageAccount storageAccount = CloudStorageAccount.Parse(TestConfiguration.Storage.ConnectionString); CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient(); CloudBlobContainer = cloudBlobClient.GetContainerReference(ContainerName); await CloudBlobContainer.CreateIfNotExistsAsync().ConfigureAwait(false); diff --git a/e2e/test/Helpers/TestDevice.cs b/e2e/test/Helpers/TestDevice.cs index 0f78f7c292..215100d669 100644 --- a/e2e/test/Helpers/TestDevice.cs +++ b/e2e/test/Helpers/TestDevice.cs @@ -72,7 +72,7 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str string deviceName = "E2E_" + prefix + Guid.NewGuid(); // Delete existing devices named this way and create a new one. - using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); s_logger.Trace($"{nameof(GetTestDeviceAsync)}: Creating device {deviceName} with type {type}."); Client.IAuthenticationMethod auth = null; @@ -84,11 +84,11 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str { X509Thumbprint = new X509Thumbprint { - PrimaryThumbprint = Configuration.IoTHub.GetCertificateWithPrivateKey().Thumbprint + PrimaryThumbprint = TestConfiguration.IoTHub.GetCertificateWithPrivateKey().Thumbprint } }; - auth = new DeviceAuthenticationWithX509Certificate(deviceName, Configuration.IoTHub.GetCertificateWithPrivateKey()); + auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); } Device device = null; @@ -118,7 +118,7 @@ public string ConnectionString { get { - string iotHubHostName = GetHostName(Configuration.IoTHub.ConnectionString); + string iotHubHostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); return $"HostName={iotHubHostName};DeviceId={Device.Id};SharedAccessKey={Device.Authentication.SymmetricKey.PrimaryKey}"; } } @@ -126,7 +126,7 @@ public string ConnectionString /// /// Used in conjunction with DeviceClient.Create() /// - public string IoTHubHostName => GetHostName(Configuration.IoTHub.ConnectionString); + public string IoTHubHostName => GetHostName(TestConfiguration.IoTHub.ConnectionString); /// /// Device Id @@ -171,7 +171,7 @@ public DeviceClient CreateDeviceClient(ITransportSettings[] transportSettings, C } else { - deviceClient = DeviceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, Device.Id, transportSettings, options); + deviceClient = DeviceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, Device.Id, transportSettings, options); s_logger.Trace($"{nameof(CreateDeviceClient)}: Created {nameof(DeviceClient)} {Device.Id} from IoTHub connection string: ID={TestLogger.IdOf(deviceClient)}"); } } @@ -186,7 +186,7 @@ public DeviceClient CreateDeviceClient(ITransportSettings[] transportSettings, C public async Task RemoveDeviceAsync() { - using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await rm.RemoveDeviceAsync(Id).ConfigureAwait(false); } } diff --git a/e2e/test/Helpers/TestModule.cs b/e2e/test/Helpers/TestModule.cs index 72a0caaa2f..42f8c36ee9 100644 --- a/e2e/test/Helpers/TestModule.cs +++ b/e2e/test/Helpers/TestModule.cs @@ -28,7 +28,7 @@ public static async Task GetTestModuleAsync(string deviceNamePrefix, string deviceName = testDevice.Id; string moduleName = "E2E_" + moduleNamePrefix + Guid.NewGuid(); - using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); logger.Trace($"{nameof(GetTestModuleAsync)}: Creating module for device {deviceName}."); var requestModule = new Module(deviceName, moduleName); @@ -49,7 +49,7 @@ public string ConnectionString { get { - string iotHubHostName = GetHostName(Configuration.IoTHub.ConnectionString); + string iotHubHostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); return $"HostName={iotHubHostName};DeviceId={_module.DeviceId};ModuleId={_module.Id};SharedAccessKey={_module.Authentication.SymmetricKey.PrimaryKey}"; } } diff --git a/e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs b/e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs similarity index 94% rename from e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs rename to e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs index 43b6bb67ae..6f4b93b100 100644 --- a/e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs +++ b/e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs @@ -3,7 +3,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static class AzureSecurityCenterForIoTLogAnalytics { diff --git a/e2e/test/config/Configuration.IoTHub.cs b/e2e/test/config/TestConfiguration.IoTHub.cs similarity index 99% rename from e2e/test/config/Configuration.IoTHub.cs rename to e2e/test/config/TestConfiguration.IoTHub.cs index 88565486c4..768bb1eb97 100644 --- a/e2e/test/config/Configuration.IoTHub.cs +++ b/e2e/test/config/TestConfiguration.IoTHub.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static partial class IoTHub { diff --git a/e2e/test/config/Configuration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs similarity index 97% rename from e2e/test/config/Configuration.Provisioning.cs rename to e2e/test/config/TestConfiguration.Provisioning.cs index fc0f036d46..a65f27e557 100644 --- a/e2e/test/config/Configuration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static partial class Provisioning { diff --git a/e2e/test/config/Configuration.Storage.cs b/e2e/test/config/TestConfiguration.Storage.cs similarity index 85% rename from e2e/test/config/Configuration.Storage.cs rename to e2e/test/config/TestConfiguration.Storage.cs index b633840691..7d9b4a9f17 100644 --- a/e2e/test/config/Configuration.Storage.cs +++ b/e2e/test/config/TestConfiguration.Storage.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static class Storage { @@ -17,7 +17,7 @@ public static class Storage static Storage() { - ConnectionString = Configuration.GetValue("STORAGE_ACCOUNT_CONNECTION_STRING"); + ConnectionString = TestConfiguration.GetValue("STORAGE_ACCOUNT_CONNECTION_STRING"); Name = s_saName.Match(ConnectionString).Value; Key = s_saKey.Match(ConnectionString).Value; } diff --git a/e2e/test/config/Configuration.cs b/e2e/test/config/TestConfiguration.cs similarity index 98% rename from e2e/test/config/Configuration.cs rename to e2e/test/config/TestConfiguration.cs index cf8ed2db34..3f7cb89e21 100644 --- a/e2e/test/config/Configuration.cs +++ b/e2e/test/config/TestConfiguration.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { private static string GetValue(string envName, string defaultValue = null) { diff --git a/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs b/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs index 8fea36bb4b..2e20e6038f 100644 --- a/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs +++ b/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs @@ -122,7 +122,7 @@ private async Task DeviceCombinedClientOperationsAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize service client for service-side operations - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Message payload and properties for C2D operation var messagesSent = new Dictionary>(); diff --git a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs index f7e83f8a9e..63e894cb6f 100644 --- a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs +++ b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs @@ -109,7 +109,7 @@ private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix + $"_{Guid.NewGuid()}").ConfigureAwait(false); string deviceConnectionString = testDevice.ConnectionString; - var config = new Configuration.IoTHub.ConnectionStringParser(deviceConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); string deviceId = config.DeviceID; ConnectionStatus? status = null; @@ -139,7 +139,7 @@ private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( Assert.IsNotNull(twin); // Delete/disable the device in IoT Hub. This should trigger the ConnectionStatusChangesHandler. - using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await registryManagerOperation(registryManager, deviceId).ConfigureAwait(false); } @@ -198,7 +198,7 @@ private async Task ModuleClient_Gives_ConnectionStatus_DeviceDisabled_Base( Assert.IsNotNull(twin); // Delete/disable the device in IoT Hub. - using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await registryManagerOperation(registryManager, testModule.DeviceId).ConfigureAwait(false); } diff --git a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs index 96c04852bf..2698350976 100644 --- a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs +++ b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs @@ -28,7 +28,7 @@ public class DeviceClientX509AuthenticationE2ETests : E2EMsTestBase public DeviceClientX509AuthenticationE2ETests() { - _hostName = GetHostName(Configuration.IoTHub.ConnectionString); + _hostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); } [LoggedTestMethod] @@ -148,12 +148,12 @@ public async Task X509_Cert_Chain_Install_Test_MQTT_TCP() { // arrange var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(Configuration.IoTHub.GetRootCACertificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate2Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); var auth = new DeviceAuthenticationWithX509Certificate( - Configuration.IoTHub.X509ChainDeviceName, - Configuration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + TestConfiguration.IoTHub.X509ChainDeviceName, + TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), chainCerts); using var deviceClient = DeviceClient.Create( _hostName, @@ -173,12 +173,12 @@ public async Task X509_Cert_Chain_Install_Test_AMQP_TCP() { // arrange var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(Configuration.IoTHub.GetRootCACertificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate2Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); var auth = new DeviceAuthenticationWithX509Certificate( - Configuration.IoTHub.X509ChainDeviceName, - Configuration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + TestConfiguration.IoTHub.X509ChainDeviceName, + TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), chainCerts); using var deviceClient = DeviceClient.Create( _hostName, @@ -293,7 +293,7 @@ private async Task X509InvalidDeviceIdOpenAsyncTwiceTest(Client.TransportType tr private DeviceClient CreateDeviceClientWithInvalidId(Client.TransportType transportType) { string deviceName = $"DEVICE_NOT_EXIST_{Guid.NewGuid()}"; - var auth = new DeviceAuthenticationWithX509Certificate(deviceName, Configuration.IoTHub.GetCertificateWithPrivateKey()); + var auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); return DeviceClient.Create(_hostName, auth, transportType); } } diff --git a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs index f98db132d4..516d8ed6d9 100644 --- a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs +++ b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs @@ -30,7 +30,7 @@ public async Task DeviceClient_Not_Exist_AMQP() { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - var config = new Configuration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId=device_id_not_exist;SharedAccessKey={config.SharedAccessKey}", Client.TransportType.Amqp_Tcp_Only)) { await deviceClient.OpenAsync().ConfigureAwait(false); @@ -43,7 +43,7 @@ public async Task DeviceClient_Bad_Credentials_AMQP() { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - var config = new Configuration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); string invalidKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("invalid_key")); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId={config.DeviceID};SharedAccessKey={invalidKey}", Client.TransportType.Amqp_Tcp_Only)) { @@ -82,7 +82,7 @@ public async Task DeviceClient_TokenConnectionDoubleRelease_Ok() string deviceConnectionString = testDevice.ConnectionString; - var config = new Configuration.IoTHub.ConnectionStringParser(deviceConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); string iotHub = config.IotHubHostName; string deviceId = config.DeviceID; string key = config.SharedAccessKey; diff --git a/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs index e02914dda0..2f0d65dff5 100644 --- a/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs @@ -13,6 +13,6 @@ namespace Microsoft.Azure.Devices.E2ETests [TestCategory("LongRunning")] public partial class FaultInjectionPoolAmqpTests : E2EMsTestBase { - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; } } diff --git a/e2e/test/iothub/FileUploadE2ETests.cs b/e2e/test/iothub/FileUploadE2ETests.cs index 3c5f09740c..3a0309a525 100644 --- a/e2e/test/iothub/FileUploadE2ETests.cs +++ b/e2e/test/iothub/FileUploadE2ETests.cs @@ -103,7 +103,7 @@ public async Task FileUpload_SmallFile_Http_GranularSteps_Proxy() using var fileStreamSource = new FileStream(filename, FileMode.Open, FileAccess.Read); var fileUploadTransportSettings = new Http1TransportSettings() { - Proxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; await UploadFileGranularAsync(fileStreamSource, filename, fileUploadTransportSettings).ConfigureAwait(false); @@ -124,7 +124,7 @@ private async Task UploadFileGranularAsync(Stream source, string filename, Http1 if (x509auth) { - X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, Client.TransportType.Http1); @@ -165,7 +165,7 @@ private async Task UploadFileAsync(Client.TransportType transport, string filena DeviceClient deviceClient; if (x509auth) { - X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); @@ -197,7 +197,7 @@ private async Task GetSasUriAsync(Client.TransportType transport, string blobNam DeviceClient deviceClient; if (x509auth) { - X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); diff --git a/e2e/test/iothub/SasCredentialAuthenticationTests.cs b/e2e/test/iothub/SasCredentialAuthenticationTests.cs index 09881b57bb..827f05ec2b 100644 --- a/e2e/test/iothub/SasCredentialAuthenticationTests.cs +++ b/e2e/test/iothub/SasCredentialAuthenticationTests.cs @@ -39,9 +39,9 @@ public class SasCredentialAuthenticationTests : E2EMsTestBase public async Task RegistryManager_Http_SasCredentialAuth_Success() { // arrange - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var registryManager = RegistryManager.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); var device = new Device(Guid.NewGuid().ToString()); @@ -60,10 +60,10 @@ public async Task RegistryManager_Http_SasCredentialAuth_Success() public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() { // arrange - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); var sasCredential = new AzureSasCredential(signature); using var registryManager = RegistryManager.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), sasCredential); var device = new Device(Guid.NewGuid().ToString()); @@ -78,7 +78,7 @@ public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() { // Expected to be unauthorized exception. } - signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); sasCredential.Update(signature); Device createdDevice = await registryManager.AddDeviceAsync(device).ConfigureAwait(false); @@ -93,9 +93,9 @@ public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() public async Task JobClient_Http_SasCredentialAuth_Success() { // arrange - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var jobClient = JobClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); string jobId = "JOBSAMPLE" + Guid.NewGuid().ToString(); @@ -138,9 +138,9 @@ public async Task DigitalTwinClient_Http_SasCredentialAuth_Success() // Call openAsync() to open the device's connection, so that the ModelId is sent over Mqtt CONNECT packet. await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var digitalTwinClient = DigitalTwinClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); // act @@ -164,9 +164,9 @@ public async Task Service_Amqp_SasCredentialAuth_Success() using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var serviceClient = ServiceClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature), TransportType.Amqp); @@ -188,10 +188,10 @@ public async Task Service_Amqp_SasCredentialAuth_Renewed_Success() using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); var sasCredential = new AzureSasCredential(signature); using var serviceClient = ServiceClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), sasCredential, TransportType.Amqp); @@ -206,7 +206,7 @@ public async Task Service_Amqp_SasCredentialAuth_Renewed_Success() // Expected to get an unauthorized exception. } - signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); sasCredential.Update(signature); await serviceClient.OpenAsync().ConfigureAwait(false); using var message = new Message(Encoding.ASCII.GetBytes("Hello, Cloud!")); diff --git a/e2e/test/iothub/TokenCredentialAuthenticationTests.cs b/e2e/test/iothub/TokenCredentialAuthenticationTests.cs index 22d4448106..cbbcc0633c 100644 --- a/e2e/test/iothub/TokenCredentialAuthenticationTests.cs +++ b/e2e/test/iothub/TokenCredentialAuthenticationTests.cs @@ -40,8 +40,8 @@ public async Task RegistryManager_Http_TokenCredentialAuth_Success() { // arrange using var registryManager = RegistryManager.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential()); + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential()); var device = new Device(Guid.NewGuid().ToString()); @@ -60,8 +60,8 @@ public async Task JobClient_Http_TokenCredentialAuth_Success() { // arrange using var jobClient = JobClient.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential()); + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential()); string jobId = "JOBSAMPLE" + Guid.NewGuid().ToString(); string jobDeviceId = "JobsSample_Device"; @@ -104,8 +104,8 @@ public async Task DigitalTwinClient_Http_TokenCredentialAuth_Success() await deviceClient.OpenAsync().ConfigureAwait(false); using var digitalTwinClient = DigitalTwinClient.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential()); + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential()); // act HttpOperationResponse response = await digitalTwinClient @@ -129,8 +129,8 @@ public async Task Service_Amqp_TokenCredentialAuth_Success() await deviceClient.OpenAsync().ConfigureAwait(false); using var serviceClient = ServiceClient.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential(), + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential(), TransportType.Amqp); // act diff --git a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs index 05bb91e98e..32c58b13e0 100644 --- a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs +++ b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs @@ -38,10 +38,10 @@ public class AzureSecurityCenterForIoTLogAnalyticsClient : IDisposable | where DeviceId == ""{0}"" | where IoTRawEventId == ""{1}"""; - private readonly string _workspaceId = Configuration.AzureSecurityCenterForIoTLogAnalytics.WorkspacedId; - private readonly string _aadTenant = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadTenant; - private readonly string _appId = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadAppId; - private readonly string _appCertificate = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadAppCertificate; + private readonly string _workspaceId = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.WorkspacedId; + private readonly string _aadTenant = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadTenant; + private readonly string _appId = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadAppId; + private readonly string _appCertificate = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadAppCertificate; private readonly TimeSpan _polingInterval = TimeSpan.FromSeconds(20); private readonly TimeSpan _timeout = TimeSpan.FromMinutes(5); diff --git a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs index ebe911e96a..da01f7bc24 100644 --- a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs @@ -895,7 +895,7 @@ private async Task ReceiveMessageRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -955,7 +955,7 @@ private async Task ReceiveMessageUsingCallbackRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { diff --git a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs index 7968b17d46..81f867c9ef 100644 --- a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs @@ -33,7 +33,7 @@ private static async Task CompleteMessageMixOrder(TestDeviceType type, Client.Tr { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, s_devicePrefix, type).ConfigureAwait(false); using (DeviceClient deviceClient = testDevice.CreateDeviceClient(transport)) - using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await deviceClient.OpenAsync().ConfigureAwait(false); diff --git a/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs b/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs index 357c0dbbc8..3cdc8a4ece 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs @@ -205,7 +205,7 @@ private async Task ReceiveMessagePoolOverAmqpAsync( var messagesSent = new Dictionary>(); // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -253,7 +253,7 @@ private async Task ReceiveMessageUsingCallbackPoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { @@ -301,7 +301,7 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribePoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { diff --git a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs index b0a4516b4f..3bd205e88f 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs @@ -623,7 +623,7 @@ private async Task ReceiveSingleMessageAsync(TestDeviceType type, Client.Transpo { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -667,7 +667,7 @@ private async Task ReceiveSingleMessageWithCancellationTokenAsync(TestDeviceType { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -733,7 +733,7 @@ private async Task ReceiveSingleMessageUsingCallbackAsync(TestDeviceType type, C using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); using (msg) @@ -760,7 +760,7 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // For Mqtt - we will need to subscribe to the Mqtt receive telemetry topic // before the device can begin receiving c2d messages. @@ -837,7 +837,7 @@ private async Task ReceiveMessageUsingCallbackUpdateHandlerAsync(TestDeviceType TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Set the first C2D message handler. await deviceClient.SetReceiveMessageHandlerAsync( @@ -896,7 +896,7 @@ private async Task ReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType typ DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); @@ -939,7 +939,7 @@ private async Task DoNotReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceTyp DeviceClient deviceClient = testDevice.CreateDeviceClient(settings); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); diff --git a/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs b/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs index 9b3a72bac1..cf30a99335 100644 --- a/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs @@ -363,7 +363,7 @@ private async Task ReceiveMessageRecovery( TimeSpan delayInSec, string proxyAddress = null) { - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { await serviceClient.OpenAsync().ConfigureAwait(false); @@ -418,7 +418,7 @@ private async Task ReceiveMessageWithCallbackRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { diff --git a/e2e/test/iothub/messaging/MessageSendE2ETests.cs b/e2e/test/iothub/messaging/MessageSendE2ETests.cs index 6415c03e85..974be9733e 100644 --- a/e2e/test/iothub/messaging/MessageSendE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageSendE2ETests.cs @@ -23,7 +23,7 @@ public partial class MessageSendE2ETests : E2EMsTestBase private const int LargeMessageSizeInBytes = 255 * 1024; // The maximum message size for device to cloud messages is 256 KB. We are allowing 1 KB of buffer for message header information etc. private readonly string DevicePrefix = $"{nameof(MessageSendE2ETests)}_"; private readonly string ModulePrefix = $"{nameof(MessageSendE2ETests)}_"; - private static string ProxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static string ProxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_DeviceSendSingleMessage_Amqp() diff --git a/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs b/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs index 7546178e68..c5a89c2236 100644 --- a/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs +++ b/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Messaging public partial class MessageSendFaultInjectionTests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(MessageSendFaultInjectionTests)}_"; - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_TcpConnectionLossSendRecovery_Amqp() diff --git a/e2e/test/iothub/method/MethodE2ETests.cs b/e2e/test/iothub/method/MethodE2ETests.cs index 18bbebb510..d4b71ec71f 100644 --- a/e2e/test/iothub/method/MethodE2ETests.cs +++ b/e2e/test/iothub/method/MethodE2ETests.cs @@ -129,7 +129,7 @@ public async Task Method_ServiceSendsMethodThroughProxyWithDefaultTimeout() { var serviceClientTransportSettings = new ServiceClientTransportSettings { - HttpProxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + HttpProxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; await SendMethodAndRespondAsync( @@ -144,7 +144,7 @@ public async Task Method_ServiceSendsMethodThroughProxyWithCustomTimeout() { var serviceClientTransportSettings = new ServiceClientTransportSettings { - HttpProxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + HttpProxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; await SendMethodAndRespondAsync( @@ -159,7 +159,7 @@ await SendMethodAndRespondAsync( public async Task Method_ServiceInvokeDeviceMethodWithUnknownDeviceThrows() { // setup - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -233,7 +233,7 @@ public async Task Method_ServiceInvokeDeviceMethodWithUnknownModuleThrows() { // setup TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, "ModuleNotFoundTest").ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -282,7 +282,7 @@ await deviceClient null) .ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var c2dMethod = new CloudToDeviceMethod(commandName, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)).SetPayloadJson(null); // act @@ -313,8 +313,8 @@ public static async Task ServiceSendMethodAndVerifyNotReceivedAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); @@ -346,8 +346,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = @@ -375,8 +375,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; diff --git a/e2e/test/iothub/method/MethodFaultInjectionTests.cs b/e2e/test/iothub/method/MethodFaultInjectionTests.cs index 483e6df6e0..4b33787d59 100644 --- a/e2e/test/iothub/method/MethodFaultInjectionTests.cs +++ b/e2e/test/iothub/method/MethodFaultInjectionTests.cs @@ -214,7 +214,7 @@ private async Task ServiceSendMethodAndVerifyResponseAsync(string deviceName, st attempt++; try { - using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = diff --git a/e2e/test/iothub/service/BulkOperationsE2ETests.cs b/e2e/test/iothub/service/BulkOperationsE2ETests.cs index 97880bb878..f748b217fa 100644 --- a/e2e/test/iothub/service/BulkOperationsE2ETests.cs +++ b/e2e/test/iothub/service/BulkOperationsE2ETests.cs @@ -25,7 +25,7 @@ public async Task BulkOperations_UpdateTwins2Device_Ok() var tagValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); @@ -52,7 +52,7 @@ public async Task BulkOperations_UpdateTwins2DevicePatch_Ok() var tagValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = new Twin(); twin.DeviceId = testDevice.Id; @@ -79,7 +79,7 @@ public async Task BulkOperations_UpdateTwins2Module_Ok() var tagValue = Guid.NewGuid().ToString(); TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); @@ -108,7 +108,7 @@ public async Task BulkOperations_UpdateTwins2ModulePatch_Ok() TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twin = new Twin(); twin.DeviceId = testModule.DeviceId; twin.ModuleId = testModule.Id; diff --git a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs index 4d8aee5168..435998214f 100644 --- a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs +++ b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs @@ -25,7 +25,7 @@ public class DigitalTwinClientE2ETests : E2EMsTestBase private const string TemperatureControllerModelId = "dtmi:com:example:TemperatureController;1"; private readonly string _devicePrefix = $"E2E_{nameof(DigitalTwinClientE2ETests)}_"; - private static readonly string s_connectionString = Configuration.IoTHub.ConnectionString; + private static readonly string s_connectionString = TestConfiguration.IoTHub.ConnectionString; [LoggedTestMethod] public async Task DigitalTwinWithOnlyRootComponentOperationsAsync() diff --git a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs index 4ba550d855..0ebf0f1c5b 100644 --- a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs +++ b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs @@ -18,7 +18,7 @@ public class IoTHubCertificateValidationE2ETest : E2EMsTestBase [LoggedTestMethod] public async Task RegistryManager_QueryDevicesInvalidServiceCertificateHttp_Fails() { - var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionStringInvalidServiceCertificate); + var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); IQuery query = rm.CreateQuery("select * from devices"); var exception = await Assert.ThrowsExceptionAsync( () => query.GetNextAsTwinAsync()).ConfigureAwait(false); @@ -51,7 +51,7 @@ public async Task ServiceClient_SendMessageToDeviceInvalidServiceCertificateAmqp private static async Task TestServiceClientInvalidServiceCertificate(TransportType transport) { var service = ServiceClient.CreateFromConnectionString( - Configuration.IoTHub.ConnectionStringInvalidServiceCertificate, + TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate, transport); await service.SendAsync("testDevice1", new Message()).ConfigureAwait(false); } @@ -59,7 +59,7 @@ private static async Task TestServiceClientInvalidServiceCertificate(TransportTy [LoggedTestMethod] public async Task JobClient_ScheduleTwinUpdateInvalidServiceCertificateHttp_Fails() { - var job = JobClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionStringInvalidServiceCertificate); + var job = JobClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); var exception = await Assert.ThrowsExceptionAsync( () => job.ScheduleTwinUpdateAsync( "testDevice", @@ -129,7 +129,7 @@ private static async Task TestDeviceClientInvalidServiceCertificate(Client.Trans { using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString( - Configuration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, + TestConfiguration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, transport)) { await deviceClient.SendEventAsync(new Client.Message()).ConfigureAwait(false); diff --git a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs index da10526a10..042d6b854f 100644 --- a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs +++ b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs @@ -22,8 +22,8 @@ public class IoTHubServiceProxyE2ETests : E2EMsTestBase private readonly string DevicePrefix = $"{nameof(IoTHubServiceProxyE2ETests)}_"; private const string JobDeviceId = "JobsSample_Device"; private const string JobTestTagName = "JobsSample_Tag"; - private static string s_connectionString = Configuration.IoTHub.ConnectionString; - private static string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static string s_connectionString = TestConfiguration.IoTHub.ConnectionString; + private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private const int MaxIterationWait = 30; private static readonly TimeSpan _waitDuration = TimeSpan.FromSeconds(5); diff --git a/e2e/test/iothub/service/PnpServiceTests.cs b/e2e/test/iothub/service/PnpServiceTests.cs index ad84bb3eed..b88e2c9e5d 100644 --- a/e2e/test/iothub/service/PnpServiceTests.cs +++ b/e2e/test/iothub/service/PnpServiceTests.cs @@ -43,7 +43,7 @@ public async Task DeviceTwin_Contains_ModelId() // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -65,15 +65,15 @@ public async Task DeviceTwin_Contains_ModelId_X509() { ModelId = TestModelId, }; - string hostName = HostNameHelper.GetHostName(Configuration.IoTHub.ConnectionString); - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, Configuration.IoTHub.GetCertificateWithPrivateKey()); + string hostName = HostNameHelper.GetHostName(TestConfiguration.IoTHub.ConnectionString); + var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); using var deviceClient = DeviceClient.Create(hostName, auth, Client.TransportType.Mqtt_Tcp_Only, options); await deviceClient.OpenAsync().ConfigureAwait(false); // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -101,7 +101,7 @@ public async Task ModuleTwin_Contains_ModelId() // Act // Get module twin. - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); // Assert diff --git a/e2e/test/iothub/service/RegistryManagerE2ETests.cs b/e2e/test/iothub/service/RegistryManagerE2ETests.cs index e3a257a7c5..9d93ffccc2 100644 --- a/e2e/test/iothub/service/RegistryManagerE2ETests.cs +++ b/e2e/test/iothub/service/RegistryManagerE2ETests.cs @@ -28,10 +28,10 @@ public async Task RegistryManager_BadProxy_ThrowsException() { // arrange var registryManager = RegistryManager.CreateFromConnectionString( - Configuration.IoTHub.ConnectionString, + TestConfiguration.IoTHub.ConnectionString, new HttpTransportSettings { - Proxy = new WebProxy(Configuration.IoTHub.InvalidProxyServerAddress), + Proxy = new WebProxy(TestConfiguration.IoTHub.InvalidProxyServerAddress), }); // act @@ -47,7 +47,7 @@ public async Task RegistryManager_AddAndRemoveDeviceWithScope() string edgeId2 = _devicePrefix + Guid.NewGuid(); string deviceId = _devicePrefix + Guid.NewGuid(); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -95,7 +95,7 @@ public async Task RegistryManager_AddDeviceWithTwinWithDeviceCapabilities() { string deviceId = _devicePrefix + Guid.NewGuid(); - using (var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { var twin = new Twin { @@ -126,7 +126,7 @@ public async Task RegistryManager_BulkLifecycle() devices.Add(new Device(_devicePrefix + Guid.NewGuid())); } - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Test that you can create devices in bulk var bulkAddResult = await registryManager.AddDevices2Async(devices).ConfigureAwait(false); @@ -176,10 +176,10 @@ public async Task RegistryManager_AddDeviceWithProxy() string deviceId = _devicePrefix + Guid.NewGuid(); var transportSettings = new HttpTransportSettings { - Proxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; - using (var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, transportSettings)) + using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportSettings)) { var device = new Device(deviceId); await registryManager.AddDeviceAsync(device).ConfigureAwait(false); @@ -190,7 +190,7 @@ public async Task RegistryManager_AddDeviceWithProxy() public async Task RegistryManager_Query_Works() { // arrange - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); string deviceId = $"{_devicePrefix}{Guid.NewGuid()}"; try @@ -242,7 +242,7 @@ public async Task ModulesClient_GetModulesOnDevice() } Device device = null; - RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -285,7 +285,7 @@ public async Task ModulesClient_IdentityLifecycle() string testDeviceId = $"IdentityLifecycleDevice{Guid.NewGuid()}"; string testModuleId = $"IdentityLifecycleModule{Guid.NewGuid()}"; - RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -328,7 +328,7 @@ public async Task ModulesClient_IdentityLifecycle() [LoggedTestMethod] public async Task ModulesClient_DeviceTwinLifecycle() { - RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var module = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); try diff --git a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs index ff8fe7b9da..0a82c6b650 100644 --- a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs @@ -55,7 +55,7 @@ public async Task RegistryManager_ExportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ExportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -90,7 +90,7 @@ await registryManager ManagedIdentity identity = null; if (isUserAssignedMsi) { - string userAssignedMsiResourceId = Configuration.IoTHub.UserAssignedMsiResourceId; + string userAssignedMsiResourceId = TestConfiguration.IoTHub.UserAssignedMsiResourceId; identity = new ManagedIdentity { userAssignedIdentity = userAssignedMsiResourceId diff --git a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs index a7b47e1ae9..21bad7bb87 100644 --- a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs @@ -49,7 +49,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ImportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -88,7 +88,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag ManagedIdentity identity = null; if (isUserAssignedMsi) { - string userAssignedMsiResourceId = Configuration.IoTHub.UserAssignedMsiResourceId; + string userAssignedMsiResourceId = TestConfiguration.IoTHub.UserAssignedMsiResourceId; identity = new ManagedIdentity { userAssignedIdentity = userAssignedMsiResourceId diff --git a/e2e/test/iothub/service/ServiceClientE2ETests.cs b/e2e/test/iothub/service/ServiceClientE2ETests.cs index e9af1c4eb3..3f5d8a11f8 100644 --- a/e2e/test/iothub/service/ServiceClientE2ETests.cs +++ b/e2e/test/iothub/service/ServiceClientE2ETests.cs @@ -48,7 +48,7 @@ private async Task DefaultTimeout() private async Task TestTimeout(TimeSpan? timeout) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Stopwatch sw = new Stopwatch(); sw.Start(); @@ -72,7 +72,7 @@ public async Task ServiceClient_SendsMessage(TransportType transportType) { // arrange TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, transportType); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportType); string messageId = Guid.NewGuid().ToString(); // act and expect no exception @@ -91,7 +91,7 @@ public async Task MessageIdDefaultNotSet_SendEventDoesNotSetMessageId() { // arrange TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); string messageId = Guid.NewGuid().ToString(); // act @@ -120,7 +120,7 @@ public async Task MessageIdDefaultSetToNull_SendEventDoesNotSetMessageId() { SdkAssignsMessageId = Shared.SdkAssignsMessageId.Never, }; - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, options); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act @@ -149,7 +149,7 @@ public async Task MessageIdDefaultSetToGuid_SendEventSetMessageIdIfNotSet() { SdkAssignsMessageId = Shared.SdkAssignsMessageId.WhenUnset, }; - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, options); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act diff --git a/e2e/test/iothub/twin/TwinE2ETests.cs b/e2e/test/iothub/twin/TwinE2ETests.cs index bdea63b787..48dbbaa582 100644 --- a/e2e/test/iothub/twin/TwinE2ETests.cs +++ b/e2e/test/iothub/twin/TwinE2ETests.cs @@ -21,7 +21,7 @@ public class TwinE2ETests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(TwinE2ETests)}_"; - private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); private static readonly List s_listOfPropertyValues = new List { @@ -523,7 +523,7 @@ await deviceClient public static async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, object propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -602,7 +602,7 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetAs var propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var twinPatch = new Twin(); @@ -622,7 +622,7 @@ private async Task Twin_DeviceSetsReportedPropertyAndServiceReceivesItAsync(Clie var propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var patch = new TwinCollection(); @@ -643,7 +643,7 @@ private async Task Twin_ServiceDoesNotCreateNullPropertyInCollectionAsync(Client var propEmptyValue = "{}"; TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await deviceClient @@ -694,7 +694,7 @@ private async Task Twin_ClientHandlesRejectionInvalidPropertyNameAsync(Client.Tr var propName2 = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var exceptionThrown = false; diff --git a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs index 688303f08d..0f163d994f 100644 --- a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs +++ b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs @@ -256,7 +256,7 @@ await FaultInjection private async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, string propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -273,7 +273,7 @@ private async Task Twin_DeviceDesiredPropertyUpdateRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); var propName = Guid.NewGuid().ToString(); diff --git a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs index ba2c52a6b4..21482a1ec7 100644 --- a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs +++ b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs @@ -19,7 +19,7 @@ public class ProvisioningCertificateValidationE2ETest : E2EMsTestBase public async Task ProvisioningServiceClient_QueryInvalidServiceCertificateHttp_Fails() { using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString( - Configuration.Provisioning.ConnectionStringInvalidServiceCertificate); + TestConfiguration.Provisioning.ConnectionStringInvalidServiceCertificate); Query q = provisioningServiceClient.CreateEnrollmentGroupQuery( new QuerySpecification("SELECT * FROM enrollmentGroups")); @@ -97,9 +97,9 @@ public async Task ProvisioningDeviceClient_RegisterAsyncInvalidServiceCertificat private static async Task TestInvalidServiceCertificate(ProvisioningTransportHandler transport) { using var security = - new SecurityProviderX509Certificate(Configuration.Provisioning.GetIndividualEnrollmentCertificate()); + new SecurityProviderX509Certificate(TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate()); ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.Create( - Configuration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, + TestConfiguration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, "0ne00000001", security, transport); diff --git a/e2e/test/provisioning/ProvisioningE2ETests.cs b/e2e/test/provisioning/ProvisioningE2ETests.cs index 68947e2a49..3ad66d7812 100644 --- a/e2e/test/provisioning/ProvisioningE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningE2ETests.cs @@ -31,8 +31,8 @@ public class ProvisioningE2ETests : E2EMsTestBase private const string InvalidIdScope = "0neFFFFFFFF"; private const string PayloadJsonData = "{\"testKey\":\"testValue\"}"; private const string InvalidGlobalAddress = "httpbin.org"; - private static readonly string s_globalDeviceEndpoint = Configuration.Provisioning.GlobalDeviceEndpoint; - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private readonly string _idPrefix = $"e2e-{nameof(ProvisioningE2ETests).ToLower()}-"; private readonly VerboseTestLogger _verboseLog = VerboseTestLogger.GetInstance(); @@ -372,17 +372,17 @@ private async Task ProvisioningDeviceClientCustomAllocationPolicyAsync( bool setCustomProxy, string customServerProxy = null) { - var closeHostName = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString).HostName; + var closeHostName = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName; - ICollection iotHubsToProvisionTo = new List() { closeHostName, Configuration.Provisioning.FarAwayIotHubHostName }; + ICollection iotHubsToProvisionTo = new List() { closeHostName, TestConfiguration.Provisioning.FarAwayIotHubHostName }; string expectedDestinationHub = ""; - if (closeHostName.Length > Configuration.Provisioning.FarAwayIotHubHostName.Length) + if (closeHostName.Length > TestConfiguration.Provisioning.FarAwayIotHubHostName.Length) { expectedDestinationHub = closeHostName; } - else if (closeHostName.Length < Configuration.Provisioning.FarAwayIotHubHostName.Length) + else if (closeHostName.Length < TestConfiguration.Provisioning.FarAwayIotHubHostName.Length) { - expectedDestinationHub = Configuration.Provisioning.FarAwayIotHubHostName; + expectedDestinationHub = TestConfiguration.Provisioning.FarAwayIotHubHostName; } else { @@ -452,7 +452,7 @@ public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( string proxyServerAddress = null) { //Default reprovisioning settings: Hashed allocation, no reprovision policy, hub names, or custom allocation policy - var iothubs = new List() { IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString).HostName }; + var iothubs = new List() { IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName }; await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( transportType, attestationType, @@ -500,7 +500,7 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); @@ -555,7 +555,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc var customAllocationDefinition = new CustomAllocationDefinition { - WebhookUrl = Configuration.Provisioning.CustomAllocationPolicyWebhook, + WebhookUrl = TestConfiguration.Provisioning.CustomAllocationPolicyWebhook, ApiVersion = "2019-03-31", }; @@ -578,7 +578,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); @@ -645,7 +645,7 @@ public async Task ProvisioningDeviceClient_InvalidRegistrationId_TpmRegister_Fai using SecurityProvider security = new SecurityProviderTpmSimulator("invalidregistrationid"); var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); @@ -812,7 +812,7 @@ private async Task ProvisioningDeviceClientInvalidGlobalAddressRegisterFailAsync ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( InvalidGlobalAddress, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); @@ -893,7 +893,7 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromNameAsync)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); switch (attestationType) { @@ -903,7 +903,7 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; @@ -922,12 +922,12 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta switch (enrollmentType) { case EnrollmentType.Individual: - certificate = Configuration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); break; case EnrollmentType.Group: - certificate = Configuration.Provisioning.GetGroupEnrollmentCertificate(); - collection = Configuration.Provisioning.GetGroupEnrollmentChain(); + certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); + collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); break; default: diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 6ec0b7eca2..79a540cb82 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Provisioning [TestCategory("DPS")] public class ProvisioningServiceClientE2ETests : E2EMsTestBase { - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private static readonly string s_devicePrefix = $"E2E_{nameof(ProvisioningServiceClientE2ETests)}_"; #pragma warning disable CA1823 @@ -114,7 +114,7 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, attestationType, null, AllocationPolicy.Static, null, null, null); AttestationMechanism attestationMechanism = await provisioningServiceClient.GetIndividualEnrollmentAttestationAsync(individualEnrollment.RegistrationId); @@ -144,7 +144,7 @@ public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(A public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); string groupId = AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); EnrollmentGroup enrollmentGroup = await CreateEnrollmentGroup(provisioningServiceClient, attestationType, groupId, null, AllocationPolicy.Static, null, null, null); @@ -268,7 +268,7 @@ public static async Task CreateIndividualEnrollment(Provis using (var tpmSim = new SecurityProviderTpmSimulator(registrationId)) { string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { Capabilities = capabilities, @@ -347,7 +347,7 @@ public static ProvisioningServiceClient CreateProvisioningService(string proxySe transportSettings.Proxy = new WebProxy(proxyServerAddress); } - return ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString, transportSettings); + return ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString, transportSettings); } /// diff --git a/e2e/test/provisioning/ReprovisioningE2ETests.cs b/e2e/test/provisioning/ReprovisioningE2ETests.cs index 60247d4123..c281e88f34 100644 --- a/e2e/test/provisioning/ReprovisioningE2ETests.cs +++ b/e2e/test/provisioning/ReprovisioningE2ETests.cs @@ -28,8 +28,8 @@ namespace Microsoft.Azure.Devices.E2ETests.Provisioning public class ReprovisioningE2ETests : E2EMsTestBase { private const int PassingTimeoutMiliseconds = 10 * 60 * 1000; - private static readonly string s_globalDeviceEndpoint = Configuration.Provisioning.GlobalDeviceEndpoint; - private static string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; + private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private readonly string _devicePrefix = $"E2E_{nameof(ProvisioningE2ETests)}_"; #pragma warning disable CA1823 @@ -211,8 +211,8 @@ public async Task ProvisioningDeviceClient_ReprovisioningBlockingWorks_MqttWs_Sy /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -223,8 +223,8 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client. /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = true, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -234,8 +234,8 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.T /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_DoNotReprovision(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = false }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -283,7 +283,7 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); @@ -348,7 +348,7 @@ private async Task CreateSecurityProviderFromName(AttestationM { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromName)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); switch (attestationType) { @@ -358,7 +358,7 @@ private async Task CreateSecurityProviderFromName(AttestationM string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; @@ -377,12 +377,12 @@ private async Task CreateSecurityProviderFromName(AttestationM switch (enrollmentType) { case EnrollmentType.Individual: - certificate = Configuration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); break; case EnrollmentType.Group: - certificate = Configuration.Provisioning.GetGroupEnrollmentCertificate(); - collection = Configuration.Provisioning.GetGroupEnrollmentChain(); + certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); + collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); break; default: @@ -501,7 +501,7 @@ private void ConfirmDeviceInExpectedHub(DeviceRegistrationResult result, Reprovi if (allocationPolicy == AllocationPolicy.GeoLatency) { - Assert.AreNotEqual(result.AssignedHub, Configuration.Provisioning.FarAwayIotHubHostName); + Assert.AreNotEqual(result.AssignedHub, TestConfiguration.Provisioning.FarAwayIotHubHostName); } } else From dee1dec0694493ea79de50c1c4291c5521abc864 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Fri, 18 Jun 2021 09:32:49 -0700 Subject: [PATCH 20/77] Revert "Rename E2ETests.Configuration to TestConfiguration to avoid SDK type name conflict" This reverts commit 75034e5b3139de7e58b927b036cdc9b6c6fe2115. --- e2e/test/Helpers/StorageContainer.cs | 2 +- e2e/test/Helpers/TestDevice.cs | 14 ++++---- e2e/test/Helpers/TestModule.cs | 4 +-- ....AzureSecurityCenterForIoTLogAnalytics.cs} | 2 +- ...tion.IoTHub.cs => Configuration.IoTHub.cs} | 2 +- ...oning.cs => Configuration.Provisioning.cs} | 2 +- ...on.Storage.cs => Configuration.Storage.cs} | 4 +-- ...{TestConfiguration.cs => Configuration.cs} | 2 +- .../CombinedClientOperationsPoolAmqpTests.cs | 2 +- .../ConnectionStatusChangeHandlerTests.cs | 6 ++-- .../DeviceClientX509AuthenticationE2ETests.cs | 24 ++++++------- e2e/test/iothub/DeviceTokenRefreshE2ETests.cs | 6 ++-- .../iothub/FaultInjectionPoolAmqpTests.cs | 2 +- e2e/test/iothub/FileUploadE2ETests.cs | 8 ++--- .../SasCredentialAuthenticationTests.cs | 28 +++++++-------- .../TokenCredentialAuthenticationTests.cs | 16 ++++----- ...eSecurityCenterForIoTLogAnalyticsClient.cs | 8 ++--- ...ssageReceiveFaultInjectionPoolAmqpTests.cs | 4 +-- .../messaging/MessageFeedbackE2ETests.cs | 2 +- .../MessageReceiveE2EPoolAmqpTests.cs | 6 ++-- .../messaging/MessageReceiveE2ETests.cs | 14 ++++---- .../MessageReceiveFaultInjectionTests.cs | 4 +-- .../iothub/messaging/MessageSendE2ETests.cs | 2 +- .../MessageSendFaultInjectionTests.cs | 2 +- e2e/test/iothub/method/MethodE2ETests.cs | 22 ++++++------ .../method/MethodFaultInjectionTests.cs | 2 +- .../iothub/service/BulkOperationsE2ETests.cs | 8 ++--- .../service/DigitalTwinClientE2ETests.cs | 2 +- .../IoTHubCertificateValidationE2ETest.cs | 8 ++--- .../service/IoTHubServiceProxyE2ETests.cs | 4 +-- e2e/test/iothub/service/PnpServiceTests.cs | 10 +++--- .../iothub/service/RegistryManagerE2ETests.cs | 22 ++++++------ .../RegistryManagerExportDevicesTests.cs | 4 +-- .../RegistryManagerImportDevicesTests.cs | 4 +-- .../iothub/service/ServiceClientE2ETests.cs | 10 +++--- e2e/test/iothub/twin/TwinE2ETests.cs | 12 +++---- .../iothub/twin/TwinFaultInjectionTests.cs | 4 +-- ...rovisioningCertificateValidationE2ETest.cs | 6 ++-- e2e/test/provisioning/ProvisioningE2ETests.cs | 36 +++++++++---------- .../ProvisioningServiceClientE2ETests.cs | 10 +++--- .../provisioning/ReprovisioningE2ETests.cs | 30 ++++++++-------- 41 files changed, 180 insertions(+), 180 deletions(-) rename e2e/test/config/{TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs => Configuration.AzureSecurityCenterForIoTLogAnalytics.cs} (94%) rename e2e/test/config/{TestConfiguration.IoTHub.cs => Configuration.IoTHub.cs} (99%) rename e2e/test/config/{TestConfiguration.Provisioning.cs => Configuration.Provisioning.cs} (97%) rename e2e/test/config/{TestConfiguration.Storage.cs => Configuration.Storage.cs} (85%) rename e2e/test/config/{TestConfiguration.cs => Configuration.cs} (98%) diff --git a/e2e/test/Helpers/StorageContainer.cs b/e2e/test/Helpers/StorageContainer.cs index ac450b81cc..94bb4cfcfe 100644 --- a/e2e/test/Helpers/StorageContainer.cs +++ b/e2e/test/Helpers/StorageContainer.cs @@ -136,7 +136,7 @@ protected virtual void Dispose(bool disposing) private async Task InitializeAsync() { - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(TestConfiguration.Storage.ConnectionString); + CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Configuration.Storage.ConnectionString); CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient(); CloudBlobContainer = cloudBlobClient.GetContainerReference(ContainerName); await CloudBlobContainer.CreateIfNotExistsAsync().ConfigureAwait(false); diff --git a/e2e/test/Helpers/TestDevice.cs b/e2e/test/Helpers/TestDevice.cs index 215100d669..0f78f7c292 100644 --- a/e2e/test/Helpers/TestDevice.cs +++ b/e2e/test/Helpers/TestDevice.cs @@ -72,7 +72,7 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str string deviceName = "E2E_" + prefix + Guid.NewGuid(); // Delete existing devices named this way and create a new one. - using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); s_logger.Trace($"{nameof(GetTestDeviceAsync)}: Creating device {deviceName} with type {type}."); Client.IAuthenticationMethod auth = null; @@ -84,11 +84,11 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str { X509Thumbprint = new X509Thumbprint { - PrimaryThumbprint = TestConfiguration.IoTHub.GetCertificateWithPrivateKey().Thumbprint + PrimaryThumbprint = Configuration.IoTHub.GetCertificateWithPrivateKey().Thumbprint } }; - auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); + auth = new DeviceAuthenticationWithX509Certificate(deviceName, Configuration.IoTHub.GetCertificateWithPrivateKey()); } Device device = null; @@ -118,7 +118,7 @@ public string ConnectionString { get { - string iotHubHostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); + string iotHubHostName = GetHostName(Configuration.IoTHub.ConnectionString); return $"HostName={iotHubHostName};DeviceId={Device.Id};SharedAccessKey={Device.Authentication.SymmetricKey.PrimaryKey}"; } } @@ -126,7 +126,7 @@ public string ConnectionString /// /// Used in conjunction with DeviceClient.Create() /// - public string IoTHubHostName => GetHostName(TestConfiguration.IoTHub.ConnectionString); + public string IoTHubHostName => GetHostName(Configuration.IoTHub.ConnectionString); /// /// Device Id @@ -171,7 +171,7 @@ public DeviceClient CreateDeviceClient(ITransportSettings[] transportSettings, C } else { - deviceClient = DeviceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, Device.Id, transportSettings, options); + deviceClient = DeviceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, Device.Id, transportSettings, options); s_logger.Trace($"{nameof(CreateDeviceClient)}: Created {nameof(DeviceClient)} {Device.Id} from IoTHub connection string: ID={TestLogger.IdOf(deviceClient)}"); } } @@ -186,7 +186,7 @@ public DeviceClient CreateDeviceClient(ITransportSettings[] transportSettings, C public async Task RemoveDeviceAsync() { - using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); await rm.RemoveDeviceAsync(Id).ConfigureAwait(false); } } diff --git a/e2e/test/Helpers/TestModule.cs b/e2e/test/Helpers/TestModule.cs index 42f8c36ee9..72a0caaa2f 100644 --- a/e2e/test/Helpers/TestModule.cs +++ b/e2e/test/Helpers/TestModule.cs @@ -28,7 +28,7 @@ public static async Task GetTestModuleAsync(string deviceNamePrefix, string deviceName = testDevice.Id; string moduleName = "E2E_" + moduleNamePrefix + Guid.NewGuid(); - using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); logger.Trace($"{nameof(GetTestModuleAsync)}: Creating module for device {deviceName}."); var requestModule = new Module(deviceName, moduleName); @@ -49,7 +49,7 @@ public string ConnectionString { get { - string iotHubHostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); + string iotHubHostName = GetHostName(Configuration.IoTHub.ConnectionString); return $"HostName={iotHubHostName};DeviceId={_module.DeviceId};ModuleId={_module.Id};SharedAccessKey={_module.Authentication.SymmetricKey.PrimaryKey}"; } } diff --git a/e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs b/e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs similarity index 94% rename from e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs rename to e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs index 6f4b93b100..43b6bb67ae 100644 --- a/e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs +++ b/e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs @@ -3,7 +3,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class TestConfiguration + public static partial class Configuration { public static class AzureSecurityCenterForIoTLogAnalytics { diff --git a/e2e/test/config/TestConfiguration.IoTHub.cs b/e2e/test/config/Configuration.IoTHub.cs similarity index 99% rename from e2e/test/config/TestConfiguration.IoTHub.cs rename to e2e/test/config/Configuration.IoTHub.cs index 768bb1eb97..88565486c4 100644 --- a/e2e/test/config/TestConfiguration.IoTHub.cs +++ b/e2e/test/config/Configuration.IoTHub.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class TestConfiguration + public static partial class Configuration { public static partial class IoTHub { diff --git a/e2e/test/config/TestConfiguration.Provisioning.cs b/e2e/test/config/Configuration.Provisioning.cs similarity index 97% rename from e2e/test/config/TestConfiguration.Provisioning.cs rename to e2e/test/config/Configuration.Provisioning.cs index a65f27e557..fc0f036d46 100644 --- a/e2e/test/config/TestConfiguration.Provisioning.cs +++ b/e2e/test/config/Configuration.Provisioning.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class TestConfiguration + public static partial class Configuration { public static partial class Provisioning { diff --git a/e2e/test/config/TestConfiguration.Storage.cs b/e2e/test/config/Configuration.Storage.cs similarity index 85% rename from e2e/test/config/TestConfiguration.Storage.cs rename to e2e/test/config/Configuration.Storage.cs index 7d9b4a9f17..b633840691 100644 --- a/e2e/test/config/TestConfiguration.Storage.cs +++ b/e2e/test/config/Configuration.Storage.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class TestConfiguration + public static partial class Configuration { public static class Storage { @@ -17,7 +17,7 @@ public static class Storage static Storage() { - ConnectionString = TestConfiguration.GetValue("STORAGE_ACCOUNT_CONNECTION_STRING"); + ConnectionString = Configuration.GetValue("STORAGE_ACCOUNT_CONNECTION_STRING"); Name = s_saName.Match(ConnectionString).Value; Key = s_saKey.Match(ConnectionString).Value; } diff --git a/e2e/test/config/TestConfiguration.cs b/e2e/test/config/Configuration.cs similarity index 98% rename from e2e/test/config/TestConfiguration.cs rename to e2e/test/config/Configuration.cs index 3f7cb89e21..cf8ed2db34 100644 --- a/e2e/test/config/TestConfiguration.cs +++ b/e2e/test/config/Configuration.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class TestConfiguration + public static partial class Configuration { private static string GetValue(string envName, string defaultValue = null) { diff --git a/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs b/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs index 2e20e6038f..8fea36bb4b 100644 --- a/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs +++ b/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs @@ -122,7 +122,7 @@ private async Task DeviceCombinedClientOperationsAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize service client for service-side operations - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // Message payload and properties for C2D operation var messagesSent = new Dictionary>(); diff --git a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs index 63e894cb6f..f7e83f8a9e 100644 --- a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs +++ b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs @@ -109,7 +109,7 @@ private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix + $"_{Guid.NewGuid()}").ConfigureAwait(false); string deviceConnectionString = testDevice.ConnectionString; - var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); + var config = new Configuration.IoTHub.ConnectionStringParser(deviceConnectionString); string deviceId = config.DeviceID; ConnectionStatus? status = null; @@ -139,7 +139,7 @@ private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( Assert.IsNotNull(twin); // Delete/disable the device in IoT Hub. This should trigger the ConnectionStatusChangesHandler. - using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) + using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) { await registryManagerOperation(registryManager, deviceId).ConfigureAwait(false); } @@ -198,7 +198,7 @@ private async Task ModuleClient_Gives_ConnectionStatus_DeviceDisabled_Base( Assert.IsNotNull(twin); // Delete/disable the device in IoT Hub. - using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) + using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) { await registryManagerOperation(registryManager, testModule.DeviceId).ConfigureAwait(false); } diff --git a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs index 2698350976..96c04852bf 100644 --- a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs +++ b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs @@ -28,7 +28,7 @@ public class DeviceClientX509AuthenticationE2ETests : E2EMsTestBase public DeviceClientX509AuthenticationE2ETests() { - _hostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); + _hostName = GetHostName(Configuration.IoTHub.ConnectionString); } [LoggedTestMethod] @@ -148,12 +148,12 @@ public async Task X509_Cert_Chain_Install_Test_MQTT_TCP() { // arrange var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); + chainCerts.Add(Configuration.IoTHub.GetRootCACertificate()); + chainCerts.Add(Configuration.IoTHub.GetIntermediate1Certificate()); + chainCerts.Add(Configuration.IoTHub.GetIntermediate2Certificate()); var auth = new DeviceAuthenticationWithX509Certificate( - TestConfiguration.IoTHub.X509ChainDeviceName, - TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + Configuration.IoTHub.X509ChainDeviceName, + Configuration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), chainCerts); using var deviceClient = DeviceClient.Create( _hostName, @@ -173,12 +173,12 @@ public async Task X509_Cert_Chain_Install_Test_AMQP_TCP() { // arrange var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); + chainCerts.Add(Configuration.IoTHub.GetRootCACertificate()); + chainCerts.Add(Configuration.IoTHub.GetIntermediate1Certificate()); + chainCerts.Add(Configuration.IoTHub.GetIntermediate2Certificate()); var auth = new DeviceAuthenticationWithX509Certificate( - TestConfiguration.IoTHub.X509ChainDeviceName, - TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + Configuration.IoTHub.X509ChainDeviceName, + Configuration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), chainCerts); using var deviceClient = DeviceClient.Create( _hostName, @@ -293,7 +293,7 @@ private async Task X509InvalidDeviceIdOpenAsyncTwiceTest(Client.TransportType tr private DeviceClient CreateDeviceClientWithInvalidId(Client.TransportType transportType) { string deviceName = $"DEVICE_NOT_EXIST_{Guid.NewGuid()}"; - var auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); + var auth = new DeviceAuthenticationWithX509Certificate(deviceName, Configuration.IoTHub.GetCertificateWithPrivateKey()); return DeviceClient.Create(_hostName, auth, transportType); } } diff --git a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs index 516d8ed6d9..f98db132d4 100644 --- a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs +++ b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs @@ -30,7 +30,7 @@ public async Task DeviceClient_Not_Exist_AMQP() { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); + var config = new Configuration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId=device_id_not_exist;SharedAccessKey={config.SharedAccessKey}", Client.TransportType.Amqp_Tcp_Only)) { await deviceClient.OpenAsync().ConfigureAwait(false); @@ -43,7 +43,7 @@ public async Task DeviceClient_Bad_Credentials_AMQP() { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); + var config = new Configuration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); string invalidKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("invalid_key")); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId={config.DeviceID};SharedAccessKey={invalidKey}", Client.TransportType.Amqp_Tcp_Only)) { @@ -82,7 +82,7 @@ public async Task DeviceClient_TokenConnectionDoubleRelease_Ok() string deviceConnectionString = testDevice.ConnectionString; - var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); + var config = new Configuration.IoTHub.ConnectionStringParser(deviceConnectionString); string iotHub = config.IotHubHostName; string deviceId = config.DeviceID; string key = config.SharedAccessKey; diff --git a/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs index 2f0d65dff5..e02914dda0 100644 --- a/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs @@ -13,6 +13,6 @@ namespace Microsoft.Azure.Devices.E2ETests [TestCategory("LongRunning")] public partial class FaultInjectionPoolAmqpTests : E2EMsTestBase { - private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; } } diff --git a/e2e/test/iothub/FileUploadE2ETests.cs b/e2e/test/iothub/FileUploadE2ETests.cs index 3a0309a525..3c5f09740c 100644 --- a/e2e/test/iothub/FileUploadE2ETests.cs +++ b/e2e/test/iothub/FileUploadE2ETests.cs @@ -103,7 +103,7 @@ public async Task FileUpload_SmallFile_Http_GranularSteps_Proxy() using var fileStreamSource = new FileStream(filename, FileMode.Open, FileAccess.Read); var fileUploadTransportSettings = new Http1TransportSettings() { - Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) + Proxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) }; await UploadFileGranularAsync(fileStreamSource, filename, fileUploadTransportSettings).ConfigureAwait(false); @@ -124,7 +124,7 @@ private async Task UploadFileGranularAsync(Stream source, string filename, Http1 if (x509auth) { - X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, Client.TransportType.Http1); @@ -165,7 +165,7 @@ private async Task UploadFileAsync(Client.TransportType transport, string filena DeviceClient deviceClient; if (x509auth) { - X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); @@ -197,7 +197,7 @@ private async Task GetSasUriAsync(Client.TransportType transport, string blobNam DeviceClient deviceClient; if (x509auth) { - X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); diff --git a/e2e/test/iothub/SasCredentialAuthenticationTests.cs b/e2e/test/iothub/SasCredentialAuthenticationTests.cs index 827f05ec2b..09881b57bb 100644 --- a/e2e/test/iothub/SasCredentialAuthenticationTests.cs +++ b/e2e/test/iothub/SasCredentialAuthenticationTests.cs @@ -39,9 +39,9 @@ public class SasCredentialAuthenticationTests : E2EMsTestBase public async Task RegistryManager_Http_SasCredentialAuth_Success() { // arrange - string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var registryManager = RegistryManager.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); var device = new Device(Guid.NewGuid().ToString()); @@ -60,10 +60,10 @@ public async Task RegistryManager_Http_SasCredentialAuth_Success() public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() { // arrange - string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); + string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); var sasCredential = new AzureSasCredential(signature); using var registryManager = RegistryManager.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetIotHubHostName(), sasCredential); var device = new Device(Guid.NewGuid().ToString()); @@ -78,7 +78,7 @@ public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() { // Expected to be unauthorized exception. } - signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); sasCredential.Update(signature); Device createdDevice = await registryManager.AddDeviceAsync(device).ConfigureAwait(false); @@ -93,9 +93,9 @@ public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() public async Task JobClient_Http_SasCredentialAuth_Success() { // arrange - string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var jobClient = JobClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); string jobId = "JOBSAMPLE" + Guid.NewGuid().ToString(); @@ -138,9 +138,9 @@ public async Task DigitalTwinClient_Http_SasCredentialAuth_Success() // Call openAsync() to open the device's connection, so that the ModelId is sent over Mqtt CONNECT packet. await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var digitalTwinClient = DigitalTwinClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); // act @@ -164,9 +164,9 @@ public async Task Service_Amqp_SasCredentialAuth_Success() using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var serviceClient = ServiceClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature), TransportType.Amqp); @@ -188,10 +188,10 @@ public async Task Service_Amqp_SasCredentialAuth_Renewed_Success() using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); + string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); var sasCredential = new AzureSasCredential(signature); using var serviceClient = ServiceClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetIotHubHostName(), sasCredential, TransportType.Amqp); @@ -206,7 +206,7 @@ public async Task Service_Amqp_SasCredentialAuth_Renewed_Success() // Expected to get an unauthorized exception. } - signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); sasCredential.Update(signature); await serviceClient.OpenAsync().ConfigureAwait(false); using var message = new Message(Encoding.ASCII.GetBytes("Hello, Cloud!")); diff --git a/e2e/test/iothub/TokenCredentialAuthenticationTests.cs b/e2e/test/iothub/TokenCredentialAuthenticationTests.cs index cbbcc0633c..22d4448106 100644 --- a/e2e/test/iothub/TokenCredentialAuthenticationTests.cs +++ b/e2e/test/iothub/TokenCredentialAuthenticationTests.cs @@ -40,8 +40,8 @@ public async Task RegistryManager_Http_TokenCredentialAuth_Success() { // arrange using var registryManager = RegistryManager.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), - TestConfiguration.IoTHub.GetClientSecretCredential()); + Configuration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetClientSecretCredential()); var device = new Device(Guid.NewGuid().ToString()); @@ -60,8 +60,8 @@ public async Task JobClient_Http_TokenCredentialAuth_Success() { // arrange using var jobClient = JobClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), - TestConfiguration.IoTHub.GetClientSecretCredential()); + Configuration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetClientSecretCredential()); string jobId = "JOBSAMPLE" + Guid.NewGuid().ToString(); string jobDeviceId = "JobsSample_Device"; @@ -104,8 +104,8 @@ public async Task DigitalTwinClient_Http_TokenCredentialAuth_Success() await deviceClient.OpenAsync().ConfigureAwait(false); using var digitalTwinClient = DigitalTwinClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), - TestConfiguration.IoTHub.GetClientSecretCredential()); + Configuration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetClientSecretCredential()); // act HttpOperationResponse response = await digitalTwinClient @@ -129,8 +129,8 @@ public async Task Service_Amqp_TokenCredentialAuth_Success() await deviceClient.OpenAsync().ConfigureAwait(false); using var serviceClient = ServiceClient.Create( - TestConfiguration.IoTHub.GetIotHubHostName(), - TestConfiguration.IoTHub.GetClientSecretCredential(), + Configuration.IoTHub.GetIotHubHostName(), + Configuration.IoTHub.GetClientSecretCredential(), TransportType.Amqp); // act diff --git a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs index 32c58b13e0..05bb91e98e 100644 --- a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs +++ b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs @@ -38,10 +38,10 @@ public class AzureSecurityCenterForIoTLogAnalyticsClient : IDisposable | where DeviceId == ""{0}"" | where IoTRawEventId == ""{1}"""; - private readonly string _workspaceId = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.WorkspacedId; - private readonly string _aadTenant = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadTenant; - private readonly string _appId = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadAppId; - private readonly string _appCertificate = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadAppCertificate; + private readonly string _workspaceId = Configuration.AzureSecurityCenterForIoTLogAnalytics.WorkspacedId; + private readonly string _aadTenant = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadTenant; + private readonly string _appId = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadAppId; + private readonly string _appCertificate = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadAppCertificate; private readonly TimeSpan _polingInterval = TimeSpan.FromSeconds(20); private readonly TimeSpan _timeout = TimeSpan.FromMinutes(5); diff --git a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs index da01f7bc24..ebe911e96a 100644 --- a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs @@ -895,7 +895,7 @@ private async Task ReceiveMessageRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -955,7 +955,7 @@ private async Task ReceiveMessageUsingCallbackRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { diff --git a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs index 81f867c9ef..7968b17d46 100644 --- a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs @@ -33,7 +33,7 @@ private static async Task CompleteMessageMixOrder(TestDeviceType type, Client.Tr { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, s_devicePrefix, type).ConfigureAwait(false); using (DeviceClient deviceClient = testDevice.CreateDeviceClient(transport)) - using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) + using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) { await deviceClient.OpenAsync().ConfigureAwait(false); diff --git a/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs b/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs index 3cdc8a4ece..357c0dbbc8 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs @@ -205,7 +205,7 @@ private async Task ReceiveMessagePoolOverAmqpAsync( var messagesSent = new Dictionary>(); // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -253,7 +253,7 @@ private async Task ReceiveMessageUsingCallbackPoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { @@ -301,7 +301,7 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribePoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { diff --git a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs index 3bd205e88f..b0a4516b4f 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs @@ -623,7 +623,7 @@ private async Task ReceiveSingleMessageAsync(TestDeviceType type, Client.Transpo { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -667,7 +667,7 @@ private async Task ReceiveSingleMessageWithCancellationTokenAsync(TestDeviceType { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -733,7 +733,7 @@ private async Task ReceiveSingleMessageUsingCallbackAsync(TestDeviceType type, C using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); using (msg) @@ -760,7 +760,7 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // For Mqtt - we will need to subscribe to the Mqtt receive telemetry topic // before the device can begin receiving c2d messages. @@ -837,7 +837,7 @@ private async Task ReceiveMessageUsingCallbackUpdateHandlerAsync(TestDeviceType TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // Set the first C2D message handler. await deviceClient.SetReceiveMessageHandlerAsync( @@ -896,7 +896,7 @@ private async Task ReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType typ DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); @@ -939,7 +939,7 @@ private async Task DoNotReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceTyp DeviceClient deviceClient = testDevice.CreateDeviceClient(settings); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); diff --git a/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs b/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs index cf30a99335..9b3a72bac1 100644 --- a/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs @@ -363,7 +363,7 @@ private async Task ReceiveMessageRecovery( TimeSpan delayInSec, string proxyAddress = null) { - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { await serviceClient.OpenAsync().ConfigureAwait(false); @@ -418,7 +418,7 @@ private async Task ReceiveMessageWithCallbackRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { diff --git a/e2e/test/iothub/messaging/MessageSendE2ETests.cs b/e2e/test/iothub/messaging/MessageSendE2ETests.cs index 974be9733e..6415c03e85 100644 --- a/e2e/test/iothub/messaging/MessageSendE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageSendE2ETests.cs @@ -23,7 +23,7 @@ public partial class MessageSendE2ETests : E2EMsTestBase private const int LargeMessageSizeInBytes = 255 * 1024; // The maximum message size for device to cloud messages is 256 KB. We are allowing 1 KB of buffer for message header information etc. private readonly string DevicePrefix = $"{nameof(MessageSendE2ETests)}_"; private readonly string ModulePrefix = $"{nameof(MessageSendE2ETests)}_"; - private static string ProxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static string ProxyServerAddress = Configuration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_DeviceSendSingleMessage_Amqp() diff --git a/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs b/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs index c5a89c2236..7546178e68 100644 --- a/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs +++ b/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Messaging public partial class MessageSendFaultInjectionTests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(MessageSendFaultInjectionTests)}_"; - private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_TcpConnectionLossSendRecovery_Amqp() diff --git a/e2e/test/iothub/method/MethodE2ETests.cs b/e2e/test/iothub/method/MethodE2ETests.cs index d4b71ec71f..18bbebb510 100644 --- a/e2e/test/iothub/method/MethodE2ETests.cs +++ b/e2e/test/iothub/method/MethodE2ETests.cs @@ -129,7 +129,7 @@ public async Task Method_ServiceSendsMethodThroughProxyWithDefaultTimeout() { var serviceClientTransportSettings = new ServiceClientTransportSettings { - HttpProxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) + HttpProxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) }; await SendMethodAndRespondAsync( @@ -144,7 +144,7 @@ public async Task Method_ServiceSendsMethodThroughProxyWithCustomTimeout() { var serviceClientTransportSettings = new ServiceClientTransportSettings { - HttpProxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) + HttpProxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) }; await SendMethodAndRespondAsync( @@ -159,7 +159,7 @@ await SendMethodAndRespondAsync( public async Task Method_ServiceInvokeDeviceMethodWithUnknownDeviceThrows() { // setup - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -233,7 +233,7 @@ public async Task Method_ServiceInvokeDeviceMethodWithUnknownModuleThrows() { // setup TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, "ModuleNotFoundTest").ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -282,7 +282,7 @@ await deviceClient null) .ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var c2dMethod = new CloudToDeviceMethod(commandName, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)).SetPayloadJson(null); // act @@ -313,8 +313,8 @@ public static async Task ServiceSendMethodAndVerifyNotReceivedAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); @@ -346,8 +346,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = @@ -375,8 +375,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; diff --git a/e2e/test/iothub/method/MethodFaultInjectionTests.cs b/e2e/test/iothub/method/MethodFaultInjectionTests.cs index 4b33787d59..483e6df6e0 100644 --- a/e2e/test/iothub/method/MethodFaultInjectionTests.cs +++ b/e2e/test/iothub/method/MethodFaultInjectionTests.cs @@ -214,7 +214,7 @@ private async Task ServiceSendMethodAndVerifyResponseAsync(string deviceName, st attempt++; try { - using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = diff --git a/e2e/test/iothub/service/BulkOperationsE2ETests.cs b/e2e/test/iothub/service/BulkOperationsE2ETests.cs index f748b217fa..97880bb878 100644 --- a/e2e/test/iothub/service/BulkOperationsE2ETests.cs +++ b/e2e/test/iothub/service/BulkOperationsE2ETests.cs @@ -25,7 +25,7 @@ public async Task BulkOperations_UpdateTwins2Device_Ok() var tagValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); @@ -52,7 +52,7 @@ public async Task BulkOperations_UpdateTwins2DevicePatch_Ok() var tagValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Twin twin = new Twin(); twin.DeviceId = testDevice.Id; @@ -79,7 +79,7 @@ public async Task BulkOperations_UpdateTwins2Module_Ok() var tagValue = Guid.NewGuid().ToString(); TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); @@ -108,7 +108,7 @@ public async Task BulkOperations_UpdateTwins2ModulePatch_Ok() TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var twin = new Twin(); twin.DeviceId = testModule.DeviceId; twin.ModuleId = testModule.Id; diff --git a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs index 435998214f..4d8aee5168 100644 --- a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs +++ b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs @@ -25,7 +25,7 @@ public class DigitalTwinClientE2ETests : E2EMsTestBase private const string TemperatureControllerModelId = "dtmi:com:example:TemperatureController;1"; private readonly string _devicePrefix = $"E2E_{nameof(DigitalTwinClientE2ETests)}_"; - private static readonly string s_connectionString = TestConfiguration.IoTHub.ConnectionString; + private static readonly string s_connectionString = Configuration.IoTHub.ConnectionString; [LoggedTestMethod] public async Task DigitalTwinWithOnlyRootComponentOperationsAsync() diff --git a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs index 0ebf0f1c5b..4ba550d855 100644 --- a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs +++ b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs @@ -18,7 +18,7 @@ public class IoTHubCertificateValidationE2ETest : E2EMsTestBase [LoggedTestMethod] public async Task RegistryManager_QueryDevicesInvalidServiceCertificateHttp_Fails() { - var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); + var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionStringInvalidServiceCertificate); IQuery query = rm.CreateQuery("select * from devices"); var exception = await Assert.ThrowsExceptionAsync( () => query.GetNextAsTwinAsync()).ConfigureAwait(false); @@ -51,7 +51,7 @@ public async Task ServiceClient_SendMessageToDeviceInvalidServiceCertificateAmqp private static async Task TestServiceClientInvalidServiceCertificate(TransportType transport) { var service = ServiceClient.CreateFromConnectionString( - TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate, + Configuration.IoTHub.ConnectionStringInvalidServiceCertificate, transport); await service.SendAsync("testDevice1", new Message()).ConfigureAwait(false); } @@ -59,7 +59,7 @@ private static async Task TestServiceClientInvalidServiceCertificate(TransportTy [LoggedTestMethod] public async Task JobClient_ScheduleTwinUpdateInvalidServiceCertificateHttp_Fails() { - var job = JobClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); + var job = JobClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionStringInvalidServiceCertificate); var exception = await Assert.ThrowsExceptionAsync( () => job.ScheduleTwinUpdateAsync( "testDevice", @@ -129,7 +129,7 @@ private static async Task TestDeviceClientInvalidServiceCertificate(Client.Trans { using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString( - TestConfiguration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, + Configuration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, transport)) { await deviceClient.SendEventAsync(new Client.Message()).ConfigureAwait(false); diff --git a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs index 042d6b854f..da10526a10 100644 --- a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs +++ b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs @@ -22,8 +22,8 @@ public class IoTHubServiceProxyE2ETests : E2EMsTestBase private readonly string DevicePrefix = $"{nameof(IoTHubServiceProxyE2ETests)}_"; private const string JobDeviceId = "JobsSample_Device"; private const string JobTestTagName = "JobsSample_Tag"; - private static string s_connectionString = TestConfiguration.IoTHub.ConnectionString; - private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static string s_connectionString = Configuration.IoTHub.ConnectionString; + private static string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; private const int MaxIterationWait = 30; private static readonly TimeSpan _waitDuration = TimeSpan.FromSeconds(5); diff --git a/e2e/test/iothub/service/PnpServiceTests.cs b/e2e/test/iothub/service/PnpServiceTests.cs index b88e2c9e5d..ad84bb3eed 100644 --- a/e2e/test/iothub/service/PnpServiceTests.cs +++ b/e2e/test/iothub/service/PnpServiceTests.cs @@ -43,7 +43,7 @@ public async Task DeviceTwin_Contains_ModelId() // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -65,15 +65,15 @@ public async Task DeviceTwin_Contains_ModelId_X509() { ModelId = TestModelId, }; - string hostName = HostNameHelper.GetHostName(TestConfiguration.IoTHub.ConnectionString); - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); + string hostName = HostNameHelper.GetHostName(Configuration.IoTHub.ConnectionString); + var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, Configuration.IoTHub.GetCertificateWithPrivateKey()); using var deviceClient = DeviceClient.Create(hostName, auth, Client.TransportType.Mqtt_Tcp_Only, options); await deviceClient.OpenAsync().ConfigureAwait(false); // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -101,7 +101,7 @@ public async Task ModuleTwin_Contains_ModelId() // Act // Get module twin. - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); // Assert diff --git a/e2e/test/iothub/service/RegistryManagerE2ETests.cs b/e2e/test/iothub/service/RegistryManagerE2ETests.cs index 9d93ffccc2..e3a257a7c5 100644 --- a/e2e/test/iothub/service/RegistryManagerE2ETests.cs +++ b/e2e/test/iothub/service/RegistryManagerE2ETests.cs @@ -28,10 +28,10 @@ public async Task RegistryManager_BadProxy_ThrowsException() { // arrange var registryManager = RegistryManager.CreateFromConnectionString( - TestConfiguration.IoTHub.ConnectionString, + Configuration.IoTHub.ConnectionString, new HttpTransportSettings { - Proxy = new WebProxy(TestConfiguration.IoTHub.InvalidProxyServerAddress), + Proxy = new WebProxy(Configuration.IoTHub.InvalidProxyServerAddress), }); // act @@ -47,7 +47,7 @@ public async Task RegistryManager_AddAndRemoveDeviceWithScope() string edgeId2 = _devicePrefix + Guid.NewGuid(); string deviceId = _devicePrefix + Guid.NewGuid(); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); try { @@ -95,7 +95,7 @@ public async Task RegistryManager_AddDeviceWithTwinWithDeviceCapabilities() { string deviceId = _devicePrefix + Guid.NewGuid(); - using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) + using (var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) { var twin = new Twin { @@ -126,7 +126,7 @@ public async Task RegistryManager_BulkLifecycle() devices.Add(new Device(_devicePrefix + Guid.NewGuid())); } - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // Test that you can create devices in bulk var bulkAddResult = await registryManager.AddDevices2Async(devices).ConfigureAwait(false); @@ -176,10 +176,10 @@ public async Task RegistryManager_AddDeviceWithProxy() string deviceId = _devicePrefix + Guid.NewGuid(); var transportSettings = new HttpTransportSettings { - Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) + Proxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) }; - using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportSettings)) + using (var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, transportSettings)) { var device = new Device(deviceId); await registryManager.AddDeviceAsync(device).ConfigureAwait(false); @@ -190,7 +190,7 @@ public async Task RegistryManager_AddDeviceWithProxy() public async Task RegistryManager_Query_Works() { // arrange - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); string deviceId = $"{_devicePrefix}{Guid.NewGuid()}"; try @@ -242,7 +242,7 @@ public async Task ModulesClient_GetModulesOnDevice() } Device device = null; - RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); try { @@ -285,7 +285,7 @@ public async Task ModulesClient_IdentityLifecycle() string testDeviceId = $"IdentityLifecycleDevice{Guid.NewGuid()}"; string testModuleId = $"IdentityLifecycleModule{Guid.NewGuid()}"; - RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); try { @@ -328,7 +328,7 @@ public async Task ModulesClient_IdentityLifecycle() [LoggedTestMethod] public async Task ModulesClient_DeviceTwinLifecycle() { - RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var module = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); try diff --git a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs index 0a82c6b650..ff8fe7b9da 100644 --- a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs @@ -55,7 +55,7 @@ public async Task RegistryManager_ExportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ExportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -90,7 +90,7 @@ await registryManager ManagedIdentity identity = null; if (isUserAssignedMsi) { - string userAssignedMsiResourceId = TestConfiguration.IoTHub.UserAssignedMsiResourceId; + string userAssignedMsiResourceId = Configuration.IoTHub.UserAssignedMsiResourceId; identity = new ManagedIdentity { userAssignedIdentity = userAssignedMsiResourceId diff --git a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs index 21bad7bb87..a7b47e1ae9 100644 --- a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs @@ -49,7 +49,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ImportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -88,7 +88,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag ManagedIdentity identity = null; if (isUserAssignedMsi) { - string userAssignedMsiResourceId = TestConfiguration.IoTHub.UserAssignedMsiResourceId; + string userAssignedMsiResourceId = Configuration.IoTHub.UserAssignedMsiResourceId; identity = new ManagedIdentity { userAssignedIdentity = userAssignedMsiResourceId diff --git a/e2e/test/iothub/service/ServiceClientE2ETests.cs b/e2e/test/iothub/service/ServiceClientE2ETests.cs index 3f5d8a11f8..e9af1c4eb3 100644 --- a/e2e/test/iothub/service/ServiceClientE2ETests.cs +++ b/e2e/test/iothub/service/ServiceClientE2ETests.cs @@ -48,7 +48,7 @@ private async Task DefaultTimeout() private async Task TestTimeout(TimeSpan? timeout) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); Stopwatch sw = new Stopwatch(); sw.Start(); @@ -72,7 +72,7 @@ public async Task ServiceClient_SendsMessage(TransportType transportType) { // arrange TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportType); + using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, transportType); string messageId = Guid.NewGuid().ToString(); // act and expect no exception @@ -91,7 +91,7 @@ public async Task MessageIdDefaultNotSet_SendEventDoesNotSetMessageId() { // arrange TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); string messageId = Guid.NewGuid().ToString(); // act @@ -120,7 +120,7 @@ public async Task MessageIdDefaultSetToNull_SendEventDoesNotSetMessageId() { SdkAssignsMessageId = Shared.SdkAssignsMessageId.Never, }; - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); + using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act @@ -149,7 +149,7 @@ public async Task MessageIdDefaultSetToGuid_SendEventSetMessageIdIfNotSet() { SdkAssignsMessageId = Shared.SdkAssignsMessageId.WhenUnset, }; - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); + using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act diff --git a/e2e/test/iothub/twin/TwinE2ETests.cs b/e2e/test/iothub/twin/TwinE2ETests.cs index 48dbbaa582..bdea63b787 100644 --- a/e2e/test/iothub/twin/TwinE2ETests.cs +++ b/e2e/test/iothub/twin/TwinE2ETests.cs @@ -21,7 +21,7 @@ public class TwinE2ETests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(TwinE2ETests)}_"; - private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); private static readonly List s_listOfPropertyValues = new List { @@ -523,7 +523,7 @@ await deviceClient public static async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, object propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -602,7 +602,7 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetAs var propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var twinPatch = new Twin(); @@ -622,7 +622,7 @@ private async Task Twin_DeviceSetsReportedPropertyAndServiceReceivesItAsync(Clie var propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var patch = new TwinCollection(); @@ -643,7 +643,7 @@ private async Task Twin_ServiceDoesNotCreateNullPropertyInCollectionAsync(Client var propEmptyValue = "{}"; TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await deviceClient @@ -694,7 +694,7 @@ private async Task Twin_ClientHandlesRejectionInvalidPropertyNameAsync(Client.Tr var propName2 = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var exceptionThrown = false; diff --git a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs index 0f163d994f..688303f08d 100644 --- a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs +++ b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs @@ -256,7 +256,7 @@ await FaultInjection private async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, string propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -273,7 +273,7 @@ private async Task Twin_DeviceDesiredPropertyUpdateRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); var propName = Guid.NewGuid().ToString(); diff --git a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs index 21482a1ec7..ba2c52a6b4 100644 --- a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs +++ b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs @@ -19,7 +19,7 @@ public class ProvisioningCertificateValidationE2ETest : E2EMsTestBase public async Task ProvisioningServiceClient_QueryInvalidServiceCertificateHttp_Fails() { using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString( - TestConfiguration.Provisioning.ConnectionStringInvalidServiceCertificate); + Configuration.Provisioning.ConnectionStringInvalidServiceCertificate); Query q = provisioningServiceClient.CreateEnrollmentGroupQuery( new QuerySpecification("SELECT * FROM enrollmentGroups")); @@ -97,9 +97,9 @@ public async Task ProvisioningDeviceClient_RegisterAsyncInvalidServiceCertificat private static async Task TestInvalidServiceCertificate(ProvisioningTransportHandler transport) { using var security = - new SecurityProviderX509Certificate(TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate()); + new SecurityProviderX509Certificate(Configuration.Provisioning.GetIndividualEnrollmentCertificate()); ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.Create( - TestConfiguration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, + Configuration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, "0ne00000001", security, transport); diff --git a/e2e/test/provisioning/ProvisioningE2ETests.cs b/e2e/test/provisioning/ProvisioningE2ETests.cs index 3ad66d7812..68947e2a49 100644 --- a/e2e/test/provisioning/ProvisioningE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningE2ETests.cs @@ -31,8 +31,8 @@ public class ProvisioningE2ETests : E2EMsTestBase private const string InvalidIdScope = "0neFFFFFFFF"; private const string PayloadJsonData = "{\"testKey\":\"testValue\"}"; private const string InvalidGlobalAddress = "httpbin.org"; - private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; - private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static readonly string s_globalDeviceEndpoint = Configuration.Provisioning.GlobalDeviceEndpoint; + private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; private readonly string _idPrefix = $"e2e-{nameof(ProvisioningE2ETests).ToLower()}-"; private readonly VerboseTestLogger _verboseLog = VerboseTestLogger.GetInstance(); @@ -372,17 +372,17 @@ private async Task ProvisioningDeviceClientCustomAllocationPolicyAsync( bool setCustomProxy, string customServerProxy = null) { - var closeHostName = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName; + var closeHostName = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString).HostName; - ICollection iotHubsToProvisionTo = new List() { closeHostName, TestConfiguration.Provisioning.FarAwayIotHubHostName }; + ICollection iotHubsToProvisionTo = new List() { closeHostName, Configuration.Provisioning.FarAwayIotHubHostName }; string expectedDestinationHub = ""; - if (closeHostName.Length > TestConfiguration.Provisioning.FarAwayIotHubHostName.Length) + if (closeHostName.Length > Configuration.Provisioning.FarAwayIotHubHostName.Length) { expectedDestinationHub = closeHostName; } - else if (closeHostName.Length < TestConfiguration.Provisioning.FarAwayIotHubHostName.Length) + else if (closeHostName.Length < Configuration.Provisioning.FarAwayIotHubHostName.Length) { - expectedDestinationHub = TestConfiguration.Provisioning.FarAwayIotHubHostName; + expectedDestinationHub = Configuration.Provisioning.FarAwayIotHubHostName; } else { @@ -452,7 +452,7 @@ public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( string proxyServerAddress = null) { //Default reprovisioning settings: Hashed allocation, no reprovision policy, hub names, or custom allocation policy - var iothubs = new List() { IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName }; + var iothubs = new List() { IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString).HostName }; await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( transportType, attestationType, @@ -500,7 +500,7 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - TestConfiguration.Provisioning.IdScope, + Configuration.Provisioning.IdScope, security, transport); @@ -555,7 +555,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc var customAllocationDefinition = new CustomAllocationDefinition { - WebhookUrl = TestConfiguration.Provisioning.CustomAllocationPolicyWebhook, + WebhookUrl = Configuration.Provisioning.CustomAllocationPolicyWebhook, ApiVersion = "2019-03-31", }; @@ -578,7 +578,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - TestConfiguration.Provisioning.IdScope, + Configuration.Provisioning.IdScope, security, transport); using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); @@ -645,7 +645,7 @@ public async Task ProvisioningDeviceClient_InvalidRegistrationId_TpmRegister_Fai using SecurityProvider security = new SecurityProviderTpmSimulator("invalidregistrationid"); var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - TestConfiguration.Provisioning.IdScope, + Configuration.Provisioning.IdScope, security, transport); @@ -812,7 +812,7 @@ private async Task ProvisioningDeviceClientInvalidGlobalAddressRegisterFailAsync ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( InvalidGlobalAddress, - TestConfiguration.Provisioning.IdScope, + Configuration.Provisioning.IdScope, security, transport); @@ -893,7 +893,7 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromNameAsync)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); switch (attestationType) { @@ -903,7 +903,7 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; @@ -922,12 +922,12 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta switch (enrollmentType) { case EnrollmentType.Individual: - certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = Configuration.Provisioning.GetIndividualEnrollmentCertificate(); break; case EnrollmentType.Group: - certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); - collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); + certificate = Configuration.Provisioning.GetGroupEnrollmentCertificate(); + collection = Configuration.Provisioning.GetGroupEnrollmentChain(); break; default: diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 79a540cb82..6ec0b7eca2 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Provisioning [TestCategory("DPS")] public class ProvisioningServiceClientE2ETests : E2EMsTestBase { - private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; private static readonly string s_devicePrefix = $"E2E_{nameof(ProvisioningServiceClientE2ETests)}_"; #pragma warning disable CA1823 @@ -114,7 +114,7 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, attestationType, null, AllocationPolicy.Static, null, null, null); AttestationMechanism attestationMechanism = await provisioningServiceClient.GetIndividualEnrollmentAttestationAsync(individualEnrollment.RegistrationId); @@ -144,7 +144,7 @@ public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(A public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); string groupId = AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); EnrollmentGroup enrollmentGroup = await CreateEnrollmentGroup(provisioningServiceClient, attestationType, groupId, null, AllocationPolicy.Static, null, null, null); @@ -268,7 +268,7 @@ public static async Task CreateIndividualEnrollment(Provis using (var tpmSim = new SecurityProviderTpmSimulator(registrationId)) { string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { Capabilities = capabilities, @@ -347,7 +347,7 @@ public static ProvisioningServiceClient CreateProvisioningService(string proxySe transportSettings.Proxy = new WebProxy(proxyServerAddress); } - return ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString, transportSettings); + return ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString, transportSettings); } /// diff --git a/e2e/test/provisioning/ReprovisioningE2ETests.cs b/e2e/test/provisioning/ReprovisioningE2ETests.cs index c281e88f34..60247d4123 100644 --- a/e2e/test/provisioning/ReprovisioningE2ETests.cs +++ b/e2e/test/provisioning/ReprovisioningE2ETests.cs @@ -28,8 +28,8 @@ namespace Microsoft.Azure.Devices.E2ETests.Provisioning public class ReprovisioningE2ETests : E2EMsTestBase { private const int PassingTimeoutMiliseconds = 10 * 60 * 1000; - private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; - private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static readonly string s_globalDeviceEndpoint = Configuration.Provisioning.GlobalDeviceEndpoint; + private static string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; private readonly string _devicePrefix = $"E2E_{nameof(ProvisioningE2ETests)}_"; #pragma warning disable CA1823 @@ -211,8 +211,8 @@ public async Task ProvisioningDeviceClient_ReprovisioningBlockingWorks_MqttWs_Sy /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -223,8 +223,8 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client. /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = true, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -234,8 +234,8 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.T /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_DoNotReprovision(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = false }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -283,7 +283,7 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - TestConfiguration.Provisioning.IdScope, + Configuration.Provisioning.IdScope, security, transport); using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); @@ -348,7 +348,7 @@ private async Task CreateSecurityProviderFromName(AttestationM { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromName)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); switch (attestationType) { @@ -358,7 +358,7 @@ private async Task CreateSecurityProviderFromName(AttestationM string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; @@ -377,12 +377,12 @@ private async Task CreateSecurityProviderFromName(AttestationM switch (enrollmentType) { case EnrollmentType.Individual: - certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = Configuration.Provisioning.GetIndividualEnrollmentCertificate(); break; case EnrollmentType.Group: - certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); - collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); + certificate = Configuration.Provisioning.GetGroupEnrollmentCertificate(); + collection = Configuration.Provisioning.GetGroupEnrollmentChain(); break; default: @@ -501,7 +501,7 @@ private void ConfirmDeviceInExpectedHub(DeviceRegistrationResult result, Reprovi if (allocationPolicy == AllocationPolicy.GeoLatency) { - Assert.AreNotEqual(result.AssignedHub, TestConfiguration.Provisioning.FarAwayIotHubHostName); + Assert.AreNotEqual(result.AssignedHub, Configuration.Provisioning.FarAwayIotHubHostName); } } else From 4357995065fdb070ba6fcafde023a743bc9e6e98 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Fri, 18 Jun 2021 09:25:38 -0700 Subject: [PATCH 21/77] Rename E2ETests.Configuration to TestConfiguration to avoid SDK type name conflict --- e2e/test/Helpers/StorageContainer.cs | 2 +- e2e/test/Helpers/TestDevice.cs | 14 ++++---- e2e/test/Helpers/TestModule.cs | 4 +-- ....AzureSecurityCenterForIoTLogAnalytics.cs} | 2 +- ....IoTHub.cs => TestConfiguration.IoTHub.cs} | 2 +- ...g.cs => TestConfiguration.Provisioning.cs} | 2 +- ...torage.cs => TestConfiguration.Storage.cs} | 4 +-- ...{Configuration.cs => TestConfiguration.cs} | 2 +- .../CombinedClientOperationsPoolAmqpTests.cs | 2 +- .../ConnectionStatusChangeHandlerTests.cs | 6 ++-- .../DeviceClientX509AuthenticationE2ETests.cs | 24 ++++++------- e2e/test/iothub/DeviceTokenRefreshE2ETests.cs | 6 ++-- .../iothub/FaultInjectionPoolAmqpTests.cs | 2 +- e2e/test/iothub/FileUploadE2ETests.cs | 8 ++--- .../SasCredentialAuthenticationTests.cs | 28 +++++++-------- .../TokenCredentialAuthenticationTests.cs | 16 ++++----- ...eSecurityCenterForIoTLogAnalyticsClient.cs | 8 ++--- ...ssageReceiveFaultInjectionPoolAmqpTests.cs | 4 +-- .../messaging/MessageFeedbackE2ETests.cs | 2 +- .../MessageReceiveE2EPoolAmqpTests.cs | 6 ++-- .../messaging/MessageReceiveE2ETests.cs | 14 ++++---- .../MessageReceiveFaultInjectionTests.cs | 4 +-- .../iothub/messaging/MessageSendE2ETests.cs | 2 +- .../MessageSendFaultInjectionTests.cs | 2 +- e2e/test/iothub/method/MethodE2ETests.cs | 22 ++++++------ .../method/MethodFaultInjectionTests.cs | 2 +- .../iothub/service/BulkOperationsE2ETests.cs | 8 ++--- .../service/DigitalTwinClientE2ETests.cs | 2 +- .../IoTHubCertificateValidationE2ETest.cs | 8 ++--- .../service/IoTHubServiceProxyE2ETests.cs | 4 +-- e2e/test/iothub/service/PnpServiceTests.cs | 10 +++--- .../iothub/service/RegistryManagerE2ETests.cs | 22 ++++++------ .../RegistryManagerExportDevicesTests.cs | 4 +-- .../RegistryManagerImportDevicesTests.cs | 4 +-- .../iothub/service/ServiceClientE2ETests.cs | 10 +++--- e2e/test/iothub/twin/TwinE2ETests.cs | 12 +++---- .../iothub/twin/TwinFaultInjectionTests.cs | 4 +-- ...rovisioningCertificateValidationE2ETest.cs | 6 ++-- e2e/test/provisioning/ProvisioningE2ETests.cs | 36 +++++++++---------- .../ProvisioningServiceClientE2ETests.cs | 10 +++--- .../provisioning/ReprovisioningE2ETests.cs | 30 ++++++++-------- 41 files changed, 180 insertions(+), 180 deletions(-) rename e2e/test/config/{Configuration.AzureSecurityCenterForIoTLogAnalytics.cs => TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs} (94%) rename e2e/test/config/{Configuration.IoTHub.cs => TestConfiguration.IoTHub.cs} (99%) rename e2e/test/config/{Configuration.Provisioning.cs => TestConfiguration.Provisioning.cs} (97%) rename e2e/test/config/{Configuration.Storage.cs => TestConfiguration.Storage.cs} (85%) rename e2e/test/config/{Configuration.cs => TestConfiguration.cs} (98%) diff --git a/e2e/test/Helpers/StorageContainer.cs b/e2e/test/Helpers/StorageContainer.cs index 94bb4cfcfe..ac450b81cc 100644 --- a/e2e/test/Helpers/StorageContainer.cs +++ b/e2e/test/Helpers/StorageContainer.cs @@ -136,7 +136,7 @@ protected virtual void Dispose(bool disposing) private async Task InitializeAsync() { - CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Configuration.Storage.ConnectionString); + CloudStorageAccount storageAccount = CloudStorageAccount.Parse(TestConfiguration.Storage.ConnectionString); CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient(); CloudBlobContainer = cloudBlobClient.GetContainerReference(ContainerName); await CloudBlobContainer.CreateIfNotExistsAsync().ConfigureAwait(false); diff --git a/e2e/test/Helpers/TestDevice.cs b/e2e/test/Helpers/TestDevice.cs index 0f78f7c292..215100d669 100644 --- a/e2e/test/Helpers/TestDevice.cs +++ b/e2e/test/Helpers/TestDevice.cs @@ -72,7 +72,7 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str string deviceName = "E2E_" + prefix + Guid.NewGuid(); // Delete existing devices named this way and create a new one. - using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); s_logger.Trace($"{nameof(GetTestDeviceAsync)}: Creating device {deviceName} with type {type}."); Client.IAuthenticationMethod auth = null; @@ -84,11 +84,11 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str { X509Thumbprint = new X509Thumbprint { - PrimaryThumbprint = Configuration.IoTHub.GetCertificateWithPrivateKey().Thumbprint + PrimaryThumbprint = TestConfiguration.IoTHub.GetCertificateWithPrivateKey().Thumbprint } }; - auth = new DeviceAuthenticationWithX509Certificate(deviceName, Configuration.IoTHub.GetCertificateWithPrivateKey()); + auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); } Device device = null; @@ -118,7 +118,7 @@ public string ConnectionString { get { - string iotHubHostName = GetHostName(Configuration.IoTHub.ConnectionString); + string iotHubHostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); return $"HostName={iotHubHostName};DeviceId={Device.Id};SharedAccessKey={Device.Authentication.SymmetricKey.PrimaryKey}"; } } @@ -126,7 +126,7 @@ public string ConnectionString /// /// Used in conjunction with DeviceClient.Create() /// - public string IoTHubHostName => GetHostName(Configuration.IoTHub.ConnectionString); + public string IoTHubHostName => GetHostName(TestConfiguration.IoTHub.ConnectionString); /// /// Device Id @@ -171,7 +171,7 @@ public DeviceClient CreateDeviceClient(ITransportSettings[] transportSettings, C } else { - deviceClient = DeviceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, Device.Id, transportSettings, options); + deviceClient = DeviceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, Device.Id, transportSettings, options); s_logger.Trace($"{nameof(CreateDeviceClient)}: Created {nameof(DeviceClient)} {Device.Id} from IoTHub connection string: ID={TestLogger.IdOf(deviceClient)}"); } } @@ -186,7 +186,7 @@ public DeviceClient CreateDeviceClient(ITransportSettings[] transportSettings, C public async Task RemoveDeviceAsync() { - using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await rm.RemoveDeviceAsync(Id).ConfigureAwait(false); } } diff --git a/e2e/test/Helpers/TestModule.cs b/e2e/test/Helpers/TestModule.cs index 72a0caaa2f..42f8c36ee9 100644 --- a/e2e/test/Helpers/TestModule.cs +++ b/e2e/test/Helpers/TestModule.cs @@ -28,7 +28,7 @@ public static async Task GetTestModuleAsync(string deviceNamePrefix, string deviceName = testDevice.Id; string moduleName = "E2E_" + moduleNamePrefix + Guid.NewGuid(); - using var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); logger.Trace($"{nameof(GetTestModuleAsync)}: Creating module for device {deviceName}."); var requestModule = new Module(deviceName, moduleName); @@ -49,7 +49,7 @@ public string ConnectionString { get { - string iotHubHostName = GetHostName(Configuration.IoTHub.ConnectionString); + string iotHubHostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); return $"HostName={iotHubHostName};DeviceId={_module.DeviceId};ModuleId={_module.Id};SharedAccessKey={_module.Authentication.SymmetricKey.PrimaryKey}"; } } diff --git a/e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs b/e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs similarity index 94% rename from e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs rename to e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs index 43b6bb67ae..6f4b93b100 100644 --- a/e2e/test/config/Configuration.AzureSecurityCenterForIoTLogAnalytics.cs +++ b/e2e/test/config/TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.cs @@ -3,7 +3,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static class AzureSecurityCenterForIoTLogAnalytics { diff --git a/e2e/test/config/Configuration.IoTHub.cs b/e2e/test/config/TestConfiguration.IoTHub.cs similarity index 99% rename from e2e/test/config/Configuration.IoTHub.cs rename to e2e/test/config/TestConfiguration.IoTHub.cs index 88565486c4..768bb1eb97 100644 --- a/e2e/test/config/Configuration.IoTHub.cs +++ b/e2e/test/config/TestConfiguration.IoTHub.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static partial class IoTHub { diff --git a/e2e/test/config/Configuration.Provisioning.cs b/e2e/test/config/TestConfiguration.Provisioning.cs similarity index 97% rename from e2e/test/config/Configuration.Provisioning.cs rename to e2e/test/config/TestConfiguration.Provisioning.cs index fc0f036d46..a65f27e557 100644 --- a/e2e/test/config/Configuration.Provisioning.cs +++ b/e2e/test/config/TestConfiguration.Provisioning.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static partial class Provisioning { diff --git a/e2e/test/config/Configuration.Storage.cs b/e2e/test/config/TestConfiguration.Storage.cs similarity index 85% rename from e2e/test/config/Configuration.Storage.cs rename to e2e/test/config/TestConfiguration.Storage.cs index b633840691..7d9b4a9f17 100644 --- a/e2e/test/config/Configuration.Storage.cs +++ b/e2e/test/config/TestConfiguration.Storage.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { public static class Storage { @@ -17,7 +17,7 @@ public static class Storage static Storage() { - ConnectionString = Configuration.GetValue("STORAGE_ACCOUNT_CONNECTION_STRING"); + ConnectionString = TestConfiguration.GetValue("STORAGE_ACCOUNT_CONNECTION_STRING"); Name = s_saName.Match(ConnectionString).Value; Key = s_saKey.Match(ConnectionString).Value; } diff --git a/e2e/test/config/Configuration.cs b/e2e/test/config/TestConfiguration.cs similarity index 98% rename from e2e/test/config/Configuration.cs rename to e2e/test/config/TestConfiguration.cs index cf8ed2db34..3f7cb89e21 100644 --- a/e2e/test/config/Configuration.cs +++ b/e2e/test/config/TestConfiguration.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.E2ETests { - public static partial class Configuration + public static partial class TestConfiguration { private static string GetValue(string envName, string defaultValue = null) { diff --git a/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs b/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs index 8fea36bb4b..2e20e6038f 100644 --- a/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs +++ b/e2e/test/iothub/CombinedClientOperationsPoolAmqpTests.cs @@ -122,7 +122,7 @@ private async Task DeviceCombinedClientOperationsAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize service client for service-side operations - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Message payload and properties for C2D operation var messagesSent = new Dictionary>(); diff --git a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs index f7e83f8a9e..63e894cb6f 100644 --- a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs +++ b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs @@ -109,7 +109,7 @@ private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix + $"_{Guid.NewGuid()}").ConfigureAwait(false); string deviceConnectionString = testDevice.ConnectionString; - var config = new Configuration.IoTHub.ConnectionStringParser(deviceConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); string deviceId = config.DeviceID; ConnectionStatus? status = null; @@ -139,7 +139,7 @@ private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( Assert.IsNotNull(twin); // Delete/disable the device in IoT Hub. This should trigger the ConnectionStatusChangesHandler. - using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await registryManagerOperation(registryManager, deviceId).ConfigureAwait(false); } @@ -198,7 +198,7 @@ private async Task ModuleClient_Gives_ConnectionStatus_DeviceDisabled_Base( Assert.IsNotNull(twin); // Delete/disable the device in IoT Hub. - using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await registryManagerOperation(registryManager, testModule.DeviceId).ConfigureAwait(false); } diff --git a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs index 96c04852bf..2698350976 100644 --- a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs +++ b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs @@ -28,7 +28,7 @@ public class DeviceClientX509AuthenticationE2ETests : E2EMsTestBase public DeviceClientX509AuthenticationE2ETests() { - _hostName = GetHostName(Configuration.IoTHub.ConnectionString); + _hostName = GetHostName(TestConfiguration.IoTHub.ConnectionString); } [LoggedTestMethod] @@ -148,12 +148,12 @@ public async Task X509_Cert_Chain_Install_Test_MQTT_TCP() { // arrange var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(Configuration.IoTHub.GetRootCACertificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate2Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); var auth = new DeviceAuthenticationWithX509Certificate( - Configuration.IoTHub.X509ChainDeviceName, - Configuration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + TestConfiguration.IoTHub.X509ChainDeviceName, + TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), chainCerts); using var deviceClient = DeviceClient.Create( _hostName, @@ -173,12 +173,12 @@ public async Task X509_Cert_Chain_Install_Test_AMQP_TCP() { // arrange var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(Configuration.IoTHub.GetRootCACertificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(Configuration.IoTHub.GetIntermediate2Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); + chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); var auth = new DeviceAuthenticationWithX509Certificate( - Configuration.IoTHub.X509ChainDeviceName, - Configuration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + TestConfiguration.IoTHub.X509ChainDeviceName, + TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), chainCerts); using var deviceClient = DeviceClient.Create( _hostName, @@ -293,7 +293,7 @@ private async Task X509InvalidDeviceIdOpenAsyncTwiceTest(Client.TransportType tr private DeviceClient CreateDeviceClientWithInvalidId(Client.TransportType transportType) { string deviceName = $"DEVICE_NOT_EXIST_{Guid.NewGuid()}"; - var auth = new DeviceAuthenticationWithX509Certificate(deviceName, Configuration.IoTHub.GetCertificateWithPrivateKey()); + var auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); return DeviceClient.Create(_hostName, auth, transportType); } } diff --git a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs index f98db132d4..516d8ed6d9 100644 --- a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs +++ b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs @@ -30,7 +30,7 @@ public async Task DeviceClient_Not_Exist_AMQP() { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - var config = new Configuration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId=device_id_not_exist;SharedAccessKey={config.SharedAccessKey}", Client.TransportType.Amqp_Tcp_Only)) { await deviceClient.OpenAsync().ConfigureAwait(false); @@ -43,7 +43,7 @@ public async Task DeviceClient_Bad_Credentials_AMQP() { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - var config = new Configuration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); string invalidKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("invalid_key")); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId={config.DeviceID};SharedAccessKey={invalidKey}", Client.TransportType.Amqp_Tcp_Only)) { @@ -82,7 +82,7 @@ public async Task DeviceClient_TokenConnectionDoubleRelease_Ok() string deviceConnectionString = testDevice.ConnectionString; - var config = new Configuration.IoTHub.ConnectionStringParser(deviceConnectionString); + var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); string iotHub = config.IotHubHostName; string deviceId = config.DeviceID; string key = config.SharedAccessKey; diff --git a/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs index e02914dda0..2f0d65dff5 100644 --- a/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/FaultInjectionPoolAmqpTests.cs @@ -13,6 +13,6 @@ namespace Microsoft.Azure.Devices.E2ETests [TestCategory("LongRunning")] public partial class FaultInjectionPoolAmqpTests : E2EMsTestBase { - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; } } diff --git a/e2e/test/iothub/FileUploadE2ETests.cs b/e2e/test/iothub/FileUploadE2ETests.cs index 3c5f09740c..3a0309a525 100644 --- a/e2e/test/iothub/FileUploadE2ETests.cs +++ b/e2e/test/iothub/FileUploadE2ETests.cs @@ -103,7 +103,7 @@ public async Task FileUpload_SmallFile_Http_GranularSteps_Proxy() using var fileStreamSource = new FileStream(filename, FileMode.Open, FileAccess.Read); var fileUploadTransportSettings = new Http1TransportSettings() { - Proxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; await UploadFileGranularAsync(fileStreamSource, filename, fileUploadTransportSettings).ConfigureAwait(false); @@ -124,7 +124,7 @@ private async Task UploadFileGranularAsync(Stream source, string filename, Http1 if (x509auth) { - X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, Client.TransportType.Http1); @@ -165,7 +165,7 @@ private async Task UploadFileAsync(Client.TransportType transport, string filena DeviceClient deviceClient; if (x509auth) { - X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); @@ -197,7 +197,7 @@ private async Task GetSasUriAsync(Client.TransportType transport, string blobNam DeviceClient deviceClient; if (x509auth) { - X509Certificate2 cert = Configuration.IoTHub.GetCertificateWithPrivateKey(); + X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); diff --git a/e2e/test/iothub/SasCredentialAuthenticationTests.cs b/e2e/test/iothub/SasCredentialAuthenticationTests.cs index 09881b57bb..827f05ec2b 100644 --- a/e2e/test/iothub/SasCredentialAuthenticationTests.cs +++ b/e2e/test/iothub/SasCredentialAuthenticationTests.cs @@ -39,9 +39,9 @@ public class SasCredentialAuthenticationTests : E2EMsTestBase public async Task RegistryManager_Http_SasCredentialAuth_Success() { // arrange - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var registryManager = RegistryManager.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); var device = new Device(Guid.NewGuid().ToString()); @@ -60,10 +60,10 @@ public async Task RegistryManager_Http_SasCredentialAuth_Success() public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() { // arrange - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); var sasCredential = new AzureSasCredential(signature); using var registryManager = RegistryManager.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), sasCredential); var device = new Device(Guid.NewGuid().ToString()); @@ -78,7 +78,7 @@ public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() { // Expected to be unauthorized exception. } - signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); sasCredential.Update(signature); Device createdDevice = await registryManager.AddDeviceAsync(device).ConfigureAwait(false); @@ -93,9 +93,9 @@ public async Task RegistryManager_Http_SasCredentialAuth_Renewed_Success() public async Task JobClient_Http_SasCredentialAuth_Success() { // arrange - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var jobClient = JobClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); string jobId = "JOBSAMPLE" + Guid.NewGuid().ToString(); @@ -138,9 +138,9 @@ public async Task DigitalTwinClient_Http_SasCredentialAuth_Success() // Call openAsync() to open the device's connection, so that the ModelId is sent over Mqtt CONNECT packet. await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var digitalTwinClient = DigitalTwinClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature)); // act @@ -164,9 +164,9 @@ public async Task Service_Amqp_SasCredentialAuth_Success() using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); using var serviceClient = ServiceClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), new AzureSasCredential(signature), TransportType.Amqp); @@ -188,10 +188,10 @@ public async Task Service_Amqp_SasCredentialAuth_Renewed_Success() using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); - string signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); + string signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(-1)); var sasCredential = new AzureSasCredential(signature); using var serviceClient = ServiceClient.Create( - Configuration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetIotHubHostName(), sasCredential, TransportType.Amqp); @@ -206,7 +206,7 @@ public async Task Service_Amqp_SasCredentialAuth_Renewed_Success() // Expected to get an unauthorized exception. } - signature = Configuration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); + signature = TestConfiguration.IoTHub.GetIotHubSharedAccessSignature(TimeSpan.FromHours(1)); sasCredential.Update(signature); await serviceClient.OpenAsync().ConfigureAwait(false); using var message = new Message(Encoding.ASCII.GetBytes("Hello, Cloud!")); diff --git a/e2e/test/iothub/TokenCredentialAuthenticationTests.cs b/e2e/test/iothub/TokenCredentialAuthenticationTests.cs index 22d4448106..cbbcc0633c 100644 --- a/e2e/test/iothub/TokenCredentialAuthenticationTests.cs +++ b/e2e/test/iothub/TokenCredentialAuthenticationTests.cs @@ -40,8 +40,8 @@ public async Task RegistryManager_Http_TokenCredentialAuth_Success() { // arrange using var registryManager = RegistryManager.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential()); + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential()); var device = new Device(Guid.NewGuid().ToString()); @@ -60,8 +60,8 @@ public async Task JobClient_Http_TokenCredentialAuth_Success() { // arrange using var jobClient = JobClient.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential()); + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential()); string jobId = "JOBSAMPLE" + Guid.NewGuid().ToString(); string jobDeviceId = "JobsSample_Device"; @@ -104,8 +104,8 @@ public async Task DigitalTwinClient_Http_TokenCredentialAuth_Success() await deviceClient.OpenAsync().ConfigureAwait(false); using var digitalTwinClient = DigitalTwinClient.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential()); + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential()); // act HttpOperationResponse response = await digitalTwinClient @@ -129,8 +129,8 @@ public async Task Service_Amqp_TokenCredentialAuth_Success() await deviceClient.OpenAsync().ConfigureAwait(false); using var serviceClient = ServiceClient.Create( - Configuration.IoTHub.GetIotHubHostName(), - Configuration.IoTHub.GetClientSecretCredential(), + TestConfiguration.IoTHub.GetIotHubHostName(), + TestConfiguration.IoTHub.GetClientSecretCredential(), TransportType.Amqp); // act diff --git a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs index 05bb91e98e..32c58b13e0 100644 --- a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs +++ b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs @@ -38,10 +38,10 @@ public class AzureSecurityCenterForIoTLogAnalyticsClient : IDisposable | where DeviceId == ""{0}"" | where IoTRawEventId == ""{1}"""; - private readonly string _workspaceId = Configuration.AzureSecurityCenterForIoTLogAnalytics.WorkspacedId; - private readonly string _aadTenant = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadTenant; - private readonly string _appId = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadAppId; - private readonly string _appCertificate = Configuration.AzureSecurityCenterForIoTLogAnalytics.AadAppCertificate; + private readonly string _workspaceId = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.WorkspacedId; + private readonly string _aadTenant = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadTenant; + private readonly string _appId = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadAppId; + private readonly string _appCertificate = TestConfiguration.AzureSecurityCenterForIoTLogAnalytics.AadAppCertificate; private readonly TimeSpan _polingInterval = TimeSpan.FromSeconds(20); private readonly TimeSpan _timeout = TimeSpan.FromMinutes(5); diff --git a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs index ebe911e96a..da01f7bc24 100644 --- a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs @@ -895,7 +895,7 @@ private async Task ReceiveMessageRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -955,7 +955,7 @@ private async Task ReceiveMessageUsingCallbackRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { diff --git a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs index 7968b17d46..81f867c9ef 100644 --- a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs @@ -33,7 +33,7 @@ private static async Task CompleteMessageMixOrder(TestDeviceType type, Client.Tr { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, s_devicePrefix, type).ConfigureAwait(false); using (DeviceClient deviceClient = testDevice.CreateDeviceClient(transport)) - using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { await deviceClient.OpenAsync().ConfigureAwait(false); diff --git a/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs b/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs index 357c0dbbc8..3cdc8a4ece 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2EPoolAmqpTests.cs @@ -205,7 +205,7 @@ private async Task ReceiveMessagePoolOverAmqpAsync( var messagesSent = new Dictionary>(); // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -253,7 +253,7 @@ private async Task ReceiveMessageUsingCallbackPoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { @@ -301,7 +301,7 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribePoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { diff --git a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs index b0a4516b4f..3bd205e88f 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs @@ -623,7 +623,7 @@ private async Task ReceiveSingleMessageAsync(TestDeviceType type, Client.Transpo { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -667,7 +667,7 @@ private async Task ReceiveSingleMessageWithCancellationTokenAsync(TestDeviceType { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -733,7 +733,7 @@ private async Task ReceiveSingleMessageUsingCallbackAsync(TestDeviceType type, C using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); using (msg) @@ -760,7 +760,7 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // For Mqtt - we will need to subscribe to the Mqtt receive telemetry topic // before the device can begin receiving c2d messages. @@ -837,7 +837,7 @@ private async Task ReceiveMessageUsingCallbackUpdateHandlerAsync(TestDeviceType TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Set the first C2D message handler. await deviceClient.SetReceiveMessageHandlerAsync( @@ -896,7 +896,7 @@ private async Task ReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType typ DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); @@ -939,7 +939,7 @@ private async Task DoNotReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceTyp DeviceClient deviceClient = testDevice.CreateDeviceClient(settings); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); diff --git a/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs b/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs index 9b3a72bac1..cf30a99335 100644 --- a/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveFaultInjectionTests.cs @@ -363,7 +363,7 @@ private async Task ReceiveMessageRecovery( TimeSpan delayInSec, string proxyAddress = null) { - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { await serviceClient.OpenAsync().ConfigureAwait(false); @@ -418,7 +418,7 @@ private async Task ReceiveMessageWithCallbackRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { diff --git a/e2e/test/iothub/messaging/MessageSendE2ETests.cs b/e2e/test/iothub/messaging/MessageSendE2ETests.cs index 6415c03e85..974be9733e 100644 --- a/e2e/test/iothub/messaging/MessageSendE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageSendE2ETests.cs @@ -23,7 +23,7 @@ public partial class MessageSendE2ETests : E2EMsTestBase private const int LargeMessageSizeInBytes = 255 * 1024; // The maximum message size for device to cloud messages is 256 KB. We are allowing 1 KB of buffer for message header information etc. private readonly string DevicePrefix = $"{nameof(MessageSendE2ETests)}_"; private readonly string ModulePrefix = $"{nameof(MessageSendE2ETests)}_"; - private static string ProxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static string ProxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_DeviceSendSingleMessage_Amqp() diff --git a/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs b/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs index 7546178e68..c5a89c2236 100644 --- a/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs +++ b/e2e/test/iothub/messaging/MessageSendFaultInjectionTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Messaging public partial class MessageSendFaultInjectionTests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(MessageSendFaultInjectionTests)}_"; - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_TcpConnectionLossSendRecovery_Amqp() diff --git a/e2e/test/iothub/method/MethodE2ETests.cs b/e2e/test/iothub/method/MethodE2ETests.cs index 18bbebb510..d4b71ec71f 100644 --- a/e2e/test/iothub/method/MethodE2ETests.cs +++ b/e2e/test/iothub/method/MethodE2ETests.cs @@ -129,7 +129,7 @@ public async Task Method_ServiceSendsMethodThroughProxyWithDefaultTimeout() { var serviceClientTransportSettings = new ServiceClientTransportSettings { - HttpProxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + HttpProxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; await SendMethodAndRespondAsync( @@ -144,7 +144,7 @@ public async Task Method_ServiceSendsMethodThroughProxyWithCustomTimeout() { var serviceClientTransportSettings = new ServiceClientTransportSettings { - HttpProxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + HttpProxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; await SendMethodAndRespondAsync( @@ -159,7 +159,7 @@ await SendMethodAndRespondAsync( public async Task Method_ServiceInvokeDeviceMethodWithUnknownDeviceThrows() { // setup - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -233,7 +233,7 @@ public async Task Method_ServiceInvokeDeviceMethodWithUnknownModuleThrows() { // setup TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, "ModuleNotFoundTest").ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -282,7 +282,7 @@ await deviceClient null) .ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var c2dMethod = new CloudToDeviceMethod(commandName, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)).SetPayloadJson(null); // act @@ -313,8 +313,8 @@ public static async Task ServiceSendMethodAndVerifyNotReceivedAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); @@ -346,8 +346,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = @@ -375,8 +375,8 @@ public static async Task ServiceSendMethodAndVerifyResponseAsync( ServiceClientTransportSettings serviceClientTransportSettings = default) { ServiceClient serviceClient = serviceClientTransportSettings == default - ? ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString) - : ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); + ? ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString) + : ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, TransportType.Amqp, serviceClientTransportSettings); TimeSpan methodTimeout = responseTimeout == default ? s_defaultMethodTimeoutMinutes : responseTimeout; diff --git a/e2e/test/iothub/method/MethodFaultInjectionTests.cs b/e2e/test/iothub/method/MethodFaultInjectionTests.cs index 483e6df6e0..4b33787d59 100644 --- a/e2e/test/iothub/method/MethodFaultInjectionTests.cs +++ b/e2e/test/iothub/method/MethodFaultInjectionTests.cs @@ -214,7 +214,7 @@ private async Task ServiceSendMethodAndVerifyResponseAsync(string deviceName, st attempt++; try { - using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"{nameof(ServiceSendMethodAndVerifyResponseAsync)}: Invoke method {methodName}."); CloudToDeviceMethodResult response = diff --git a/e2e/test/iothub/service/BulkOperationsE2ETests.cs b/e2e/test/iothub/service/BulkOperationsE2ETests.cs index 97880bb878..f748b217fa 100644 --- a/e2e/test/iothub/service/BulkOperationsE2ETests.cs +++ b/e2e/test/iothub/service/BulkOperationsE2ETests.cs @@ -25,7 +25,7 @@ public async Task BulkOperations_UpdateTwins2Device_Ok() var tagValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); @@ -52,7 +52,7 @@ public async Task BulkOperations_UpdateTwins2DevicePatch_Ok() var tagValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = new Twin(); twin.DeviceId = testDevice.Id; @@ -79,7 +79,7 @@ public async Task BulkOperations_UpdateTwins2Module_Ok() var tagValue = Guid.NewGuid().ToString(); TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); @@ -108,7 +108,7 @@ public async Task BulkOperations_UpdateTwins2ModulePatch_Ok() TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twin = new Twin(); twin.DeviceId = testModule.DeviceId; twin.ModuleId = testModule.Id; diff --git a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs index 4d8aee5168..435998214f 100644 --- a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs +++ b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs @@ -25,7 +25,7 @@ public class DigitalTwinClientE2ETests : E2EMsTestBase private const string TemperatureControllerModelId = "dtmi:com:example:TemperatureController;1"; private readonly string _devicePrefix = $"E2E_{nameof(DigitalTwinClientE2ETests)}_"; - private static readonly string s_connectionString = Configuration.IoTHub.ConnectionString; + private static readonly string s_connectionString = TestConfiguration.IoTHub.ConnectionString; [LoggedTestMethod] public async Task DigitalTwinWithOnlyRootComponentOperationsAsync() diff --git a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs index 4ba550d855..0ebf0f1c5b 100644 --- a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs +++ b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs @@ -18,7 +18,7 @@ public class IoTHubCertificateValidationE2ETest : E2EMsTestBase [LoggedTestMethod] public async Task RegistryManager_QueryDevicesInvalidServiceCertificateHttp_Fails() { - var rm = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionStringInvalidServiceCertificate); + var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); IQuery query = rm.CreateQuery("select * from devices"); var exception = await Assert.ThrowsExceptionAsync( () => query.GetNextAsTwinAsync()).ConfigureAwait(false); @@ -51,7 +51,7 @@ public async Task ServiceClient_SendMessageToDeviceInvalidServiceCertificateAmqp private static async Task TestServiceClientInvalidServiceCertificate(TransportType transport) { var service = ServiceClient.CreateFromConnectionString( - Configuration.IoTHub.ConnectionStringInvalidServiceCertificate, + TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate, transport); await service.SendAsync("testDevice1", new Message()).ConfigureAwait(false); } @@ -59,7 +59,7 @@ private static async Task TestServiceClientInvalidServiceCertificate(TransportTy [LoggedTestMethod] public async Task JobClient_ScheduleTwinUpdateInvalidServiceCertificateHttp_Fails() { - var job = JobClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionStringInvalidServiceCertificate); + var job = JobClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); var exception = await Assert.ThrowsExceptionAsync( () => job.ScheduleTwinUpdateAsync( "testDevice", @@ -129,7 +129,7 @@ private static async Task TestDeviceClientInvalidServiceCertificate(Client.Trans { using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString( - Configuration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, + TestConfiguration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, transport)) { await deviceClient.SendEventAsync(new Client.Message()).ConfigureAwait(false); diff --git a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs index da10526a10..042d6b854f 100644 --- a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs +++ b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs @@ -22,8 +22,8 @@ public class IoTHubServiceProxyE2ETests : E2EMsTestBase private readonly string DevicePrefix = $"{nameof(IoTHubServiceProxyE2ETests)}_"; private const string JobDeviceId = "JobsSample_Device"; private const string JobTestTagName = "JobsSample_Tag"; - private static string s_connectionString = Configuration.IoTHub.ConnectionString; - private static string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static string s_connectionString = TestConfiguration.IoTHub.ConnectionString; + private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private const int MaxIterationWait = 30; private static readonly TimeSpan _waitDuration = TimeSpan.FromSeconds(5); diff --git a/e2e/test/iothub/service/PnpServiceTests.cs b/e2e/test/iothub/service/PnpServiceTests.cs index ad84bb3eed..b88e2c9e5d 100644 --- a/e2e/test/iothub/service/PnpServiceTests.cs +++ b/e2e/test/iothub/service/PnpServiceTests.cs @@ -43,7 +43,7 @@ public async Task DeviceTwin_Contains_ModelId() // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -65,15 +65,15 @@ public async Task DeviceTwin_Contains_ModelId_X509() { ModelId = TestModelId, }; - string hostName = HostNameHelper.GetHostName(Configuration.IoTHub.ConnectionString); - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, Configuration.IoTHub.GetCertificateWithPrivateKey()); + string hostName = HostNameHelper.GetHostName(TestConfiguration.IoTHub.ConnectionString); + var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); using var deviceClient = DeviceClient.Create(hostName, auth, Client.TransportType.Mqtt_Tcp_Only, options); await deviceClient.OpenAsync().ConfigureAwait(false); // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -101,7 +101,7 @@ public async Task ModuleTwin_Contains_ModelId() // Act // Get module twin. - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); // Assert diff --git a/e2e/test/iothub/service/RegistryManagerE2ETests.cs b/e2e/test/iothub/service/RegistryManagerE2ETests.cs index e3a257a7c5..9d93ffccc2 100644 --- a/e2e/test/iothub/service/RegistryManagerE2ETests.cs +++ b/e2e/test/iothub/service/RegistryManagerE2ETests.cs @@ -28,10 +28,10 @@ public async Task RegistryManager_BadProxy_ThrowsException() { // arrange var registryManager = RegistryManager.CreateFromConnectionString( - Configuration.IoTHub.ConnectionString, + TestConfiguration.IoTHub.ConnectionString, new HttpTransportSettings { - Proxy = new WebProxy(Configuration.IoTHub.InvalidProxyServerAddress), + Proxy = new WebProxy(TestConfiguration.IoTHub.InvalidProxyServerAddress), }); // act @@ -47,7 +47,7 @@ public async Task RegistryManager_AddAndRemoveDeviceWithScope() string edgeId2 = _devicePrefix + Guid.NewGuid(); string deviceId = _devicePrefix + Guid.NewGuid(); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -95,7 +95,7 @@ public async Task RegistryManager_AddDeviceWithTwinWithDeviceCapabilities() { string deviceId = _devicePrefix + Guid.NewGuid(); - using (var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString)) + using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { var twin = new Twin { @@ -126,7 +126,7 @@ public async Task RegistryManager_BulkLifecycle() devices.Add(new Device(_devicePrefix + Guid.NewGuid())); } - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Test that you can create devices in bulk var bulkAddResult = await registryManager.AddDevices2Async(devices).ConfigureAwait(false); @@ -176,10 +176,10 @@ public async Task RegistryManager_AddDeviceWithProxy() string deviceId = _devicePrefix + Guid.NewGuid(); var transportSettings = new HttpTransportSettings { - Proxy = new WebProxy(Configuration.IoTHub.ProxyServerAddress) + Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; - using (var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, transportSettings)) + using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportSettings)) { var device = new Device(deviceId); await registryManager.AddDeviceAsync(device).ConfigureAwait(false); @@ -190,7 +190,7 @@ public async Task RegistryManager_AddDeviceWithProxy() public async Task RegistryManager_Query_Works() { // arrange - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); string deviceId = $"{_devicePrefix}{Guid.NewGuid()}"; try @@ -242,7 +242,7 @@ public async Task ModulesClient_GetModulesOnDevice() } Device device = null; - RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -285,7 +285,7 @@ public async Task ModulesClient_IdentityLifecycle() string testDeviceId = $"IdentityLifecycleDevice{Guid.NewGuid()}"; string testModuleId = $"IdentityLifecycleModule{Guid.NewGuid()}"; - RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -328,7 +328,7 @@ public async Task ModulesClient_IdentityLifecycle() [LoggedTestMethod] public async Task ModulesClient_DeviceTwinLifecycle() { - RegistryManager client = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var module = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); try diff --git a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs index ff8fe7b9da..0a82c6b650 100644 --- a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs @@ -55,7 +55,7 @@ public async Task RegistryManager_ExportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ExportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -90,7 +90,7 @@ await registryManager ManagedIdentity identity = null; if (isUserAssignedMsi) { - string userAssignedMsiResourceId = Configuration.IoTHub.UserAssignedMsiResourceId; + string userAssignedMsiResourceId = TestConfiguration.IoTHub.UserAssignedMsiResourceId; identity = new ManagedIdentity { userAssignedIdentity = userAssignedMsiResourceId diff --git a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs index a7b47e1ae9..21bad7bb87 100644 --- a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs @@ -49,7 +49,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ImportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -88,7 +88,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag ManagedIdentity identity = null; if (isUserAssignedMsi) { - string userAssignedMsiResourceId = Configuration.IoTHub.UserAssignedMsiResourceId; + string userAssignedMsiResourceId = TestConfiguration.IoTHub.UserAssignedMsiResourceId; identity = new ManagedIdentity { userAssignedIdentity = userAssignedMsiResourceId diff --git a/e2e/test/iothub/service/ServiceClientE2ETests.cs b/e2e/test/iothub/service/ServiceClientE2ETests.cs index e9af1c4eb3..3f5d8a11f8 100644 --- a/e2e/test/iothub/service/ServiceClientE2ETests.cs +++ b/e2e/test/iothub/service/ServiceClientE2ETests.cs @@ -48,7 +48,7 @@ private async Task DefaultTimeout() private async Task TestTimeout(TimeSpan? timeout) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Stopwatch sw = new Stopwatch(); sw.Start(); @@ -72,7 +72,7 @@ public async Task ServiceClient_SendsMessage(TransportType transportType) { // arrange TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, transportType); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportType); string messageId = Guid.NewGuid().ToString(); // act and expect no exception @@ -91,7 +91,7 @@ public async Task MessageIdDefaultNotSet_SendEventDoesNotSetMessageId() { // arrange TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); string messageId = Guid.NewGuid().ToString(); // act @@ -120,7 +120,7 @@ public async Task MessageIdDefaultSetToNull_SendEventDoesNotSetMessageId() { SdkAssignsMessageId = Shared.SdkAssignsMessageId.Never, }; - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, options); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act @@ -149,7 +149,7 @@ public async Task MessageIdDefaultSetToGuid_SendEventSetMessageIdIfNotSet() { SdkAssignsMessageId = Shared.SdkAssignsMessageId.WhenUnset, }; - using var sender = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString, options); + using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act diff --git a/e2e/test/iothub/twin/TwinE2ETests.cs b/e2e/test/iothub/twin/TwinE2ETests.cs index bdea63b787..48dbbaa582 100644 --- a/e2e/test/iothub/twin/TwinE2ETests.cs +++ b/e2e/test/iothub/twin/TwinE2ETests.cs @@ -21,7 +21,7 @@ public class TwinE2ETests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(TwinE2ETests)}_"; - private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + private static readonly RegistryManager _registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); private static readonly List s_listOfPropertyValues = new List { @@ -523,7 +523,7 @@ await deviceClient public static async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, object propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -602,7 +602,7 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetAs var propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var twinPatch = new Twin(); @@ -622,7 +622,7 @@ private async Task Twin_DeviceSetsReportedPropertyAndServiceReceivesItAsync(Clie var propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var patch = new TwinCollection(); @@ -643,7 +643,7 @@ private async Task Twin_ServiceDoesNotCreateNullPropertyInCollectionAsync(Client var propEmptyValue = "{}"; TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await deviceClient @@ -694,7 +694,7 @@ private async Task Twin_ClientHandlesRejectionInvalidPropertyNameAsync(Client.Tr var propName2 = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var exceptionThrown = false; diff --git a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs index 688303f08d..0f163d994f 100644 --- a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs +++ b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs @@ -256,7 +256,7 @@ await FaultInjection private async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, string propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -273,7 +273,7 @@ private async Task Twin_DeviceDesiredPropertyUpdateRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); var propName = Guid.NewGuid().ToString(); diff --git a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs index ba2c52a6b4..21482a1ec7 100644 --- a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs +++ b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs @@ -19,7 +19,7 @@ public class ProvisioningCertificateValidationE2ETest : E2EMsTestBase public async Task ProvisioningServiceClient_QueryInvalidServiceCertificateHttp_Fails() { using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString( - Configuration.Provisioning.ConnectionStringInvalidServiceCertificate); + TestConfiguration.Provisioning.ConnectionStringInvalidServiceCertificate); Query q = provisioningServiceClient.CreateEnrollmentGroupQuery( new QuerySpecification("SELECT * FROM enrollmentGroups")); @@ -97,9 +97,9 @@ public async Task ProvisioningDeviceClient_RegisterAsyncInvalidServiceCertificat private static async Task TestInvalidServiceCertificate(ProvisioningTransportHandler transport) { using var security = - new SecurityProviderX509Certificate(Configuration.Provisioning.GetIndividualEnrollmentCertificate()); + new SecurityProviderX509Certificate(TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate()); ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.Create( - Configuration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, + TestConfiguration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, "0ne00000001", security, transport); diff --git a/e2e/test/provisioning/ProvisioningE2ETests.cs b/e2e/test/provisioning/ProvisioningE2ETests.cs index 68947e2a49..3ad66d7812 100644 --- a/e2e/test/provisioning/ProvisioningE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningE2ETests.cs @@ -31,8 +31,8 @@ public class ProvisioningE2ETests : E2EMsTestBase private const string InvalidIdScope = "0neFFFFFFFF"; private const string PayloadJsonData = "{\"testKey\":\"testValue\"}"; private const string InvalidGlobalAddress = "httpbin.org"; - private static readonly string s_globalDeviceEndpoint = Configuration.Provisioning.GlobalDeviceEndpoint; - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private readonly string _idPrefix = $"e2e-{nameof(ProvisioningE2ETests).ToLower()}-"; private readonly VerboseTestLogger _verboseLog = VerboseTestLogger.GetInstance(); @@ -372,17 +372,17 @@ private async Task ProvisioningDeviceClientCustomAllocationPolicyAsync( bool setCustomProxy, string customServerProxy = null) { - var closeHostName = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString).HostName; + var closeHostName = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName; - ICollection iotHubsToProvisionTo = new List() { closeHostName, Configuration.Provisioning.FarAwayIotHubHostName }; + ICollection iotHubsToProvisionTo = new List() { closeHostName, TestConfiguration.Provisioning.FarAwayIotHubHostName }; string expectedDestinationHub = ""; - if (closeHostName.Length > Configuration.Provisioning.FarAwayIotHubHostName.Length) + if (closeHostName.Length > TestConfiguration.Provisioning.FarAwayIotHubHostName.Length) { expectedDestinationHub = closeHostName; } - else if (closeHostName.Length < Configuration.Provisioning.FarAwayIotHubHostName.Length) + else if (closeHostName.Length < TestConfiguration.Provisioning.FarAwayIotHubHostName.Length) { - expectedDestinationHub = Configuration.Provisioning.FarAwayIotHubHostName; + expectedDestinationHub = TestConfiguration.Provisioning.FarAwayIotHubHostName; } else { @@ -452,7 +452,7 @@ public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( string proxyServerAddress = null) { //Default reprovisioning settings: Hashed allocation, no reprovision policy, hub names, or custom allocation policy - var iothubs = new List() { IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString).HostName }; + var iothubs = new List() { IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName }; await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( transportType, attestationType, @@ -500,7 +500,7 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); @@ -555,7 +555,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc var customAllocationDefinition = new CustomAllocationDefinition { - WebhookUrl = Configuration.Provisioning.CustomAllocationPolicyWebhook, + WebhookUrl = TestConfiguration.Provisioning.CustomAllocationPolicyWebhook, ApiVersion = "2019-03-31", }; @@ -578,7 +578,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); @@ -645,7 +645,7 @@ public async Task ProvisioningDeviceClient_InvalidRegistrationId_TpmRegister_Fai using SecurityProvider security = new SecurityProviderTpmSimulator("invalidregistrationid"); var provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); @@ -812,7 +812,7 @@ private async Task ProvisioningDeviceClientInvalidGlobalAddressRegisterFailAsync ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( InvalidGlobalAddress, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); @@ -893,7 +893,7 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromNameAsync)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); switch (attestationType) { @@ -903,7 +903,7 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; @@ -922,12 +922,12 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta switch (enrollmentType) { case EnrollmentType.Individual: - certificate = Configuration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); break; case EnrollmentType.Group: - certificate = Configuration.Provisioning.GetGroupEnrollmentCertificate(); - collection = Configuration.Provisioning.GetGroupEnrollmentChain(); + certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); + collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); break; default: diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 6ec0b7eca2..79a540cb82 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Provisioning [TestCategory("DPS")] public class ProvisioningServiceClientE2ETests : E2EMsTestBase { - private static readonly string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private static readonly string s_devicePrefix = $"E2E_{nameof(ProvisioningServiceClientE2ETests)}_"; #pragma warning disable CA1823 @@ -114,7 +114,7 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, attestationType, null, AllocationPolicy.Static, null, null, null); AttestationMechanism attestationMechanism = await provisioningServiceClient.GetIndividualEnrollmentAttestationAsync(individualEnrollment.RegistrationId); @@ -144,7 +144,7 @@ public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(A public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); string groupId = AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); EnrollmentGroup enrollmentGroup = await CreateEnrollmentGroup(provisioningServiceClient, attestationType, groupId, null, AllocationPolicy.Static, null, null, null); @@ -268,7 +268,7 @@ public static async Task CreateIndividualEnrollment(Provis using (var tpmSim = new SecurityProviderTpmSimulator(registrationId)) { string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { Capabilities = capabilities, @@ -347,7 +347,7 @@ public static ProvisioningServiceClient CreateProvisioningService(string proxySe transportSettings.Proxy = new WebProxy(proxyServerAddress); } - return ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString, transportSettings); + return ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString, transportSettings); } /// diff --git a/e2e/test/provisioning/ReprovisioningE2ETests.cs b/e2e/test/provisioning/ReprovisioningE2ETests.cs index 60247d4123..c281e88f34 100644 --- a/e2e/test/provisioning/ReprovisioningE2ETests.cs +++ b/e2e/test/provisioning/ReprovisioningE2ETests.cs @@ -28,8 +28,8 @@ namespace Microsoft.Azure.Devices.E2ETests.Provisioning public class ReprovisioningE2ETests : E2EMsTestBase { private const int PassingTimeoutMiliseconds = 10 * 60 * 1000; - private static readonly string s_globalDeviceEndpoint = Configuration.Provisioning.GlobalDeviceEndpoint; - private static string s_proxyServerAddress = Configuration.IoTHub.ProxyServerAddress; + private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; + private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; private readonly string _devicePrefix = $"E2E_{nameof(ProvisioningE2ETests)}_"; #pragma warning disable CA1823 @@ -211,8 +211,8 @@ public async Task ProvisioningDeviceClient_ReprovisioningBlockingWorks_MqttWs_Sy /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -223,8 +223,8 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client. /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = true, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -234,8 +234,8 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.T /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_DoNotReprovision(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(Configuration.IoTHub.ConnectionString); - ICollection iotHubsToStartAt = new List() { Configuration.Provisioning.FarAwayIotHubHostName }; + var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = false }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); } @@ -283,7 +283,7 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, - Configuration.Provisioning.IdScope, + TestConfiguration.Provisioning.IdScope, security, transport); using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); @@ -348,7 +348,7 @@ private async Task CreateSecurityProviderFromName(AttestationM { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromName)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); switch (attestationType) { @@ -358,7 +358,7 @@ private async Task CreateSecurityProviderFromName(AttestationM string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(Configuration.Provisioning.ConnectionString); + var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; @@ -377,12 +377,12 @@ private async Task CreateSecurityProviderFromName(AttestationM switch (enrollmentType) { case EnrollmentType.Individual: - certificate = Configuration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); break; case EnrollmentType.Group: - certificate = Configuration.Provisioning.GetGroupEnrollmentCertificate(); - collection = Configuration.Provisioning.GetGroupEnrollmentChain(); + certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); + collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); break; default: @@ -501,7 +501,7 @@ private void ConfirmDeviceInExpectedHub(DeviceRegistrationResult result, Reprovi if (allocationPolicy == AllocationPolicy.GeoLatency) { - Assert.AreNotEqual(result.AssignedHub, Configuration.Provisioning.FarAwayIotHubHostName); + Assert.AreNotEqual(result.AssignedHub, TestConfiguration.Provisioning.FarAwayIotHubHostName); } } else From ae6512156cda2404dab71e710d90fcf95f60fd1c Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Fri, 18 Jun 2021 09:53:16 -0700 Subject: [PATCH 22/77] feat(service): add support for DeviceScope to import/export job --- .../RegistryManagerExportDevicesTests.cs | 13 +++++++ iothub/service/src/Device.cs | 2 +- iothub/service/src/ExportImportDevice.cs | 39 ++++++++++++------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs index 0a82c6b650..1b1b8795be 100644 --- a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs @@ -54,6 +54,7 @@ public async Task RegistryManager_ExportDevices(StorageAuthenticationType storag // arrange StorageContainer storageContainer = null; + string edgeId = $"{nameof(RegistryManager_ExportDevices)}-Edge-{StorageContainer.GetRandomSuffix(4)}"; string deviceId = $"{nameof(RegistryManager_ExportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); @@ -71,11 +72,21 @@ public async Task RegistryManager_ExportDevices(StorageAuthenticationType storag ? storageContainer.SasUri : storageContainer.Uri; + var edge = await registryManager + .AddDeviceAsync( + new Device(edgeId) + { + Authentication = new AuthenticationMechanism { Type = AuthenticationType.Sas }, + Capabilities = new Shared.DeviceCapabilities { IotEdge = true }, + }) + .ConfigureAwait(false); + await registryManager .AddDeviceAsync( new Device(deviceId) { Authentication = new AuthenticationMechanism { Type = AuthenticationType.Sas }, + Scope = edge.Scope, }) .ConfigureAwait(false); @@ -151,6 +162,7 @@ await registryManager { Logger.Trace($"Found device in export as [{serializedDeivce}]"); foundDeviceInExport = true; + device.DeviceScope.Should().Be(edge.Scope); break; } } @@ -163,6 +175,7 @@ await registryManager storageContainer?.Dispose(); await registryManager.RemoveDeviceAsync(deviceId).ConfigureAwait(false); + await registryManager.RemoveDeviceAsync(edgeId).ConfigureAwait(false); } catch { } } diff --git a/iothub/service/src/Device.cs b/iothub/service/src/Device.cs index 610416e563..b5b3c7ccfd 100644 --- a/iothub/service/src/Device.cs +++ b/iothub/service/src/Device.cs @@ -114,7 +114,7 @@ public Device(string id) /// relationship. /// /// - /// For leaf devices, the value to set a parent edge device can be retrieved from the parent edge device's property. + /// For leaf devices, the value to set a parent edge device can be retrieved from the parent edge device's Scope property. /// /// For more information, see . /// diff --git a/iothub/service/src/ExportImportDevice.cs b/iothub/service/src/ExportImportDevice.cs index 8d8ddb7e42..eacfc12b6d 100644 --- a/iothub/service/src/ExportImportDevice.cs +++ b/iothub/service/src/ExportImportDevice.cs @@ -5,13 +5,14 @@ // --------------------------------------------------------------- using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; namespace Microsoft.Azure.Devices { /// - /// Contains device properties specified during export/import operation + /// Contains device properties specified during export/import job operation. /// public sealed class ExportImportDevice { @@ -21,7 +22,7 @@ public sealed class ExportImportDevice /// /// Property container /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Public property. No behavior changes allowed.")] + [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Public property. No behavior changes allowed.")] public sealed class PropertyContainer { /// @@ -38,14 +39,14 @@ public sealed class PropertyContainer } /// - /// Create an ExportImportDevice + /// Create an ExportImportDevice. /// public ExportImportDevice() { } /// - /// Create an ExportImportDevice + /// Create an ExportImportDevice. /// /// Device properties /// Identifies the behavior when merging a device to the registry during import actions. @@ -66,13 +67,13 @@ public ExportImportDevice(Device device, ImportMode importmode) } /// - /// Id of the device + /// Id of the device. /// [JsonProperty(PropertyName = "id", Required = Required.Always)] public string Id { get; set; } /// - /// Module Id for the object + /// Module Id for the object. /// [JsonProperty(PropertyName = "moduleId", NullValueHandling = NullValueHandling.Ignore)] public string ModuleId { get; set; } @@ -88,31 +89,31 @@ public string ETag } /// - /// ImportMode of the device + /// Import mode of the device. /// [JsonProperty(PropertyName = "importMode", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] public ImportMode ImportMode { get; set; } /// - /// Status of the device + /// Status of the device. /// [JsonProperty(PropertyName = "status", Required = Required.Always)] public DeviceStatus Status { get; set; } /// - /// StatusReason of the device + /// Status reason of the device. /// [JsonProperty(PropertyName = "statusReason", NullValueHandling = NullValueHandling.Ignore)] public string StatusReason { get; set; } /// - /// AuthenticationMechanism of the device + /// Authentication mechanism of the device. /// [JsonProperty(PropertyName = "authentication")] public AuthenticationMechanism Authentication { get; set; } /// - /// string representing a Twin ETag for the entity, as per RFC7232. + /// String representing a Twin ETag for the entity, as per RFC7232. /// [JsonProperty(PropertyName = "twinETag", NullValueHandling = NullValueHandling.Ignore)] public string TwinETag @@ -128,17 +129,29 @@ public string TwinETag public TwinCollection Tags { get; set; } /// - /// Desired and Reported property bags + /// Desired and reported property bags /// [JsonProperty(PropertyName = "properties", NullValueHandling = NullValueHandling.Ignore)] public PropertyContainer Properties { get; set; } /// - /// Status of Capabilities enabled on the device + /// Status of capabilities enabled on the device /// [JsonProperty(PropertyName = "capabilities", NullValueHandling = NullValueHandling.Ignore)] public DeviceCapabilities Capabilities { get; set; } + /// + /// The scope of the device. For edge devices, this is auto-generated and immutable. For leaf devices, set this to create child/parent + /// relationship. + /// + /// + /// For leaf devices, the value to set a parent edge device can be retrieved from the parent edge device's device scope property. + /// + /// For more information, see . + /// + [JsonProperty(PropertyName = "deviceScope", NullValueHandling = NullValueHandling.Include)] + public string DeviceScope { get; set; } + private static string SanitizeETag(string eTag) { if (!string.IsNullOrWhiteSpace(eTag)) From 48dad62bc7041303392d424af4874fea9af1d2a2 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 18 Jun 2021 14:24:58 -0700 Subject: [PATCH 23/77] fix(iot-device): Update IoT Hub CONNACK timeout to be 60 seconds --- .../src/Transport/Mqtt/MqttTransportSettings.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs index a29e9cc92a..020d9ecf41 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs @@ -26,7 +26,10 @@ public class MqttTransportSettings : ITransportSettings private const int DefaultMaxPendingInboundMessages = 50; private const QualityOfService DefaultPublishToServerQoS = QualityOfService.AtLeastOnce; private const QualityOfService DefaultReceivingQoS = QualityOfService.AtLeastOnce; - private static readonly TimeSpan s_defaultConnectArrivalTimeout = TimeSpan.FromSeconds(10); + + // The CONNACK timeout has been chosen to be 60 seconds to be in alignment with the service implemented timeout for processing connection requests. + private static readonly TimeSpan s_defaultConnectArrivalTimeout = TimeSpan.FromSeconds(60); + private static readonly TimeSpan s_defaultDeviceReceiveAckTimeout = TimeSpan.FromSeconds(300); private static readonly TimeSpan s_defaultReceiveTimeout = TimeSpan.FromMinutes(1); @@ -157,8 +160,15 @@ public bool CertificateRevocationCheck /// /// The time to wait for receiving an acknowledgment for a CONNECT packet. - /// The default is 10 seconds. + /// The default is 60 seconds. /// + /// + /// In the event that IoT Hub receives burst traffic, it will implement traffic shaping in order to process the incoming requests. + /// In such cases, during client connection the CONNECT requests can have a delay in being acknowledged and processed by IoT Hub. + /// The governs the duration the client will wait for a CONNACK packet before disconnecting and reopening the connection. + /// To know more about IoT Hub's throttling limits and traffic shaping feature, see + /// . + /// public TimeSpan ConnectArrivalTimeout { get; set; } /// @@ -239,4 +249,4 @@ public TransportType GetTransportType() /// internal string AuthenticationChain { get; set; } } -} \ No newline at end of file +} From 38870d3d8d5c2aae735458bc14c48565acc953ad Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Mon, 21 Jun 2021 09:30:08 -0700 Subject: [PATCH 24/77] feat(provisioning-device, prov-amqp, prov-mqtt, prov-https): Add support for timespan timeouts to provisioning device client (#2041) As discussed in #2036, AMQP provisioning device clients have no way to configure the timeout given to the AMQP library for operations like opening links. This adds overloads to the existing provisioning device client's registerAsync methods that allow for users to configure this timespan timeout. --- e2e/test/provisioning/ProvisioningE2ETests.cs | 72 +++++++++++++++++- .../device/src/ProvisioningDeviceClient.cs | 62 +++++++++++---- .../src/ProvisioningTransportHandler.cs | 13 ++++ .../src/ProvisioningTransportHandlerAmqp.cs | 76 ++++++++++++++----- .../src/ProvisioningTransportHandlerHttp.cs | 14 ++++ .../src/ProvisioningTransportHandlerMqtt.cs | 14 ++++ 6 files changed, 215 insertions(+), 36 deletions(-) diff --git a/e2e/test/provisioning/ProvisioningE2ETests.cs b/e2e/test/provisioning/ProvisioningE2ETests.cs index 3ad66d7812..0cd498d760 100644 --- a/e2e/test/provisioning/ProvisioningE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningE2ETests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -432,6 +433,62 @@ public async Task ProvisioningDeviceClient_ValidRegistrationId_MqttWsWithProxy_S await ProvisioningDeviceClient_ValidRegistrationId_Register_Ok(Client.TransportType.Mqtt, AttestationMechanismType.SymmetricKey, EnrollmentType.Individual, true, s_proxyServerAddress).ConfigureAwait(false); } + [LoggedTestMethod] + public async Task ProvisioningDeviceClient_ValidRegistrationId_TimeSpanTimeoutRespected_Mqtt() + { + try + { + await ProvisioningDeviceClient_ValidRegistrationId_Register_Ok(Client.TransportType.Mqtt_Tcp_Only, AttestationMechanismType.SymmetricKey, EnrollmentType.Individual, TimeSpan.Zero).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return; // expected exception was thrown, so exit the test + } + + throw new AssertFailedException("Expected an OperationCanceledException to be thrown since the timeout was set to TimeSpan.Zero"); + } + + [LoggedTestMethod] + public async Task ProvisioningDeviceClient_ValidRegistrationId_TimeSpanTimeoutRespected_Https() + { + try + { + await ProvisioningDeviceClient_ValidRegistrationId_Register_Ok(Client.TransportType.Http1, AttestationMechanismType.SymmetricKey, EnrollmentType.Individual, TimeSpan.Zero).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return; // expected exception was thrown, so exit the test + } + + throw new AssertFailedException("Expected an OperationCanceledException to be thrown since the timeout was set to TimeSpan.Zero"); + } + + [LoggedTestMethod] + public async Task ProvisioningDeviceClient_ValidRegistrationId_TimeSpanTimeoutRespected_Amqps() + { + try + { + await ProvisioningDeviceClient_ValidRegistrationId_Register_Ok(Client.TransportType.Amqp_Tcp_Only, AttestationMechanismType.SymmetricKey, EnrollmentType.Individual, TimeSpan.Zero).ConfigureAwait(false); + } + catch (ProvisioningTransportException ex) when (ex.InnerException is SocketException && ((SocketException) ex.InnerException).SocketErrorCode == SocketError.TimedOut) + { + // The expected exception is a bit different in AMQP compared to MQTT/HTTPS + return; // expected exception was thrown, so exit the test + } + + throw new AssertFailedException("Expected an OperationCanceledException to be thrown since the timeout was set to TimeSpan.Zero"); + } + + public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( + Client.TransportType transportType, + AttestationMechanismType attestationType, + EnrollmentType? enrollmentType, + TimeSpan timeout) + { + //Default reprovisioning settings: Hashed allocation, no reprovision policy, hub names, or custom allocation policy + await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync(transportType, attestationType, enrollmentType, false, null, AllocationPolicy.Hashed, null, null, null, timeout, s_proxyServerAddress).ConfigureAwait(false); + } + public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( Client.TransportType transportType, AttestationMechanismType attestationType, @@ -440,7 +497,7 @@ public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( string proxyServerAddress = null) { //Default reprovisioning settings: Hashed allocation, no reprovision policy, hub names, or custom allocation policy - await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync(transportType, attestationType, enrollmentType, setCustomProxy, null, AllocationPolicy.Hashed, null, null, null, s_proxyServerAddress).ConfigureAwait(false); + await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync(transportType, attestationType, enrollmentType, setCustomProxy, null, AllocationPolicy.Hashed, null, null, null, TimeSpan.MaxValue, proxyServerAddress).ConfigureAwait(false); } public async Task ProvisioningDeviceClient_ValidRegistrationId_Register_Ok( @@ -463,7 +520,8 @@ await ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( null, iothubs, capabilities, - s_proxyServerAddress) + TimeSpan.MaxValue, + proxyServerAddress) .ConfigureAwait(false); } @@ -477,6 +535,7 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( CustomAllocationDefinition customAllocationDefinition, ICollection iothubs, DeviceCapabilities deviceCapabilities, + TimeSpan timeout, string proxyServerAddress = null) { string groupId = _idPrefix + AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); @@ -516,7 +575,14 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( { try { - result = await provClient.RegisterAsync(cts.Token).ConfigureAwait(false); + if (timeout != TimeSpan.MaxValue) + { + result = await provClient.RegisterAsync(timeout).ConfigureAwait(false); + } + else + { + result = await provClient.RegisterAsync(cts.Token).ConfigureAwait(false); + } break; } // Catching all ProvisioningTransportException as the status code is not the same for Mqtt, Amqp and Http. diff --git a/provisioning/device/src/ProvisioningDeviceClient.cs b/provisioning/device/src/ProvisioningDeviceClient.cs index 3c7fbb3f66..1703ff729f 100644 --- a/provisioning/device/src/ProvisioningDeviceClient.cs +++ b/provisioning/device/src/ProvisioningDeviceClient.cs @@ -3,6 +3,7 @@ using Microsoft.Azure.Devices.Provisioning.Client.Transport; using Microsoft.Azure.Devices.Shared; +using System; using System.Threading; using System.Threading.Tasks; @@ -63,45 +64,79 @@ private ProvisioningDeviceClient( /// /// Registers the current device using the Device Provisioning Service and assigns it to an IoT Hub. /// + /// The maximum amount of time to allow this operation to run for before timing out. + /// + /// Due to the AMQP library used by this library uses not accepting cancellation tokens, this overload and + /// are the only overloads for this method that allow for a specified timeout to be respected in the middle of an AMQP operation such as opening + /// the AMQP connection. MQTT and HTTPS connections do not share that same limitation, though. + /// /// The registration result. - public Task RegisterAsync() + public Task RegisterAsync(TimeSpan timeout) { - return RegisterAsync(CancellationToken.None); + return RegisterAsync(null, timeout); } /// /// Registers the current device using the Device Provisioning Service and assigns it to an IoT Hub. /// - /// The optional additional data. + /// + /// The optional additional data that is passed through to the custom allocation policy webhook if + /// a custom allocation policy webhook is setup for this enrollment. + /// + /// The maximum amount of time to allow this operation to run for before timing out. + /// + /// Due to the AMQP library used by this library uses not accepting cancellation tokens, this overload and + /// are the only overloads for this method that allow for a specified timeout to be respected in the middle of an AMQP operation such as opening + /// the AMQP connection. MQTT and HTTPS connections do not share that same limitation, though. + /// /// The registration result. - public Task RegisterAsync(ProvisioningRegistrationAdditionalData data) + public Task RegisterAsync(ProvisioningRegistrationAdditionalData data, TimeSpan timeout) { - return RegisterAsync(data, CancellationToken.None); + Logging.RegisterAsync(this, _globalDeviceEndpoint, _idScope, _transport, _security); + + var request = new ProvisioningTransportRegisterMessage(_globalDeviceEndpoint, _idScope, _security, data?.JsonData) + { + ProductInfo = ProductInfo, + }; + + return _transport.RegisterAsync(request, timeout); } /// /// Registers the current device using the Device Provisioning Service and assigns it to an IoT Hub. /// /// The cancellation token. + /// + /// Due to the AMQP library used by this library uses not accepting cancellation tokens, the provided cancellation token will only be checked + /// for cancellation in between AMQP operations, and not during. In order to have a timeout for this operation that is checked during AMQP operations + /// (such as opening the connection), you must use instead. MQTT and HTTPS connections do not have the same + /// behavior as AMQP connections in this regard. MQTT and HTTPS connections will check this cancellation token for cancellation during their protocol level operations. + /// /// The registration result. - public Task RegisterAsync(CancellationToken cancellationToken) + public Task RegisterAsync(CancellationToken cancellationToken = default) { Logging.RegisterAsync(this, _globalDeviceEndpoint, _idScope, _transport, _security); - var request = new ProvisioningTransportRegisterMessage(_globalDeviceEndpoint, _idScope, _security) - { - ProductInfo = ProductInfo - }; - return _transport.RegisterAsync(request, cancellationToken); + return RegisterAsync(null, cancellationToken); } /// /// Registers the current device using the Device Provisioning Service and assigns it to an IoT Hub. /// - /// The custom content. + /// + /// The optional additional data that is passed through to the custom allocation policy webhook if + /// a custom allocation policy webhook is setup for this enrollment. + /// /// The cancellation token. + /// + /// Due to the AMQP library used by this library uses not accepting cancellation tokens, the provided cancellation token will only be checked + /// for cancellation in between AMQP operations, and not during. In order to have a timeout for this operation that is checked during AMQP operations + /// (such as opening the connection), you must use this overload instead. + /// MQTT and HTTPS connections do not have the same behavior as AMQP connections in this regard. MQTT and HTTPS connections will check this cancellation + /// token for cancellation during their protocol level operations. + /// /// The registration result. - public Task RegisterAsync(ProvisioningRegistrationAdditionalData data, CancellationToken cancellationToken) + public Task RegisterAsync(ProvisioningRegistrationAdditionalData data, CancellationToken cancellationToken = default) { Logging.RegisterAsync(this, _globalDeviceEndpoint, _idScope, _transport, _security); @@ -109,6 +144,7 @@ public Task RegisterAsync(ProvisioningRegistrationAddi { ProductInfo = ProductInfo, }; + return _transport.RegisterAsync(request, cancellationToken); } } diff --git a/provisioning/device/src/ProvisioningTransportHandler.cs b/provisioning/device/src/ProvisioningTransportHandler.cs index 8208932cad..1ddc085cfb 100644 --- a/provisioning/device/src/ProvisioningTransportHandler.cs +++ b/provisioning/device/src/ProvisioningTransportHandler.cs @@ -85,6 +85,19 @@ public virtual Task RegisterAsync( return _innerHandler.RegisterAsync(message, cancellationToken); } + /// + /// Registers a device described by the message. + /// + /// The provisioning message. + /// The maximum amount of time to allow this operation to run for before timing out. + /// The registration result. + public virtual Task RegisterAsync( + ProvisioningTransportRegisterMessage message, + TimeSpan timeout) + { + return _innerHandler.RegisterAsync(message, timeout); + } + /// /// Releases the unmanaged resources and disposes of the managed resources used by the invoker. /// diff --git a/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs b/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs index 740d4265a2..675412e509 100644 --- a/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs +++ b/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs @@ -22,7 +22,9 @@ namespace Microsoft.Azure.Devices.Provisioning.Client.Transport /// public class ProvisioningTransportHandlerAmqp : ProvisioningTransportHandler { - private static readonly TimeSpan s_defaultOperationPoolingInterval = TimeSpan.FromSeconds(2); + // This polling interval is the default time between checking if the device has reached a terminal state in its registration process + // DPS will generally send a retry-after header that overrides this default value though. + private static readonly TimeSpan s_defaultOperationPollingInterval = TimeSpan.FromSeconds(2); private static readonly TimeSpan s_timeoutConstant = TimeSpan.FromMinutes(1); /// @@ -43,6 +45,19 @@ public ProvisioningTransportHandlerAmqp( Proxy = DefaultWebProxySettings.Instance; } + /// + /// Registers a device described by the message. + /// + /// The provisioning message. + /// The maximum amount of time to allow this operation to run for before timing out. + /// The registration result. + public override async Task RegisterAsync( + ProvisioningTransportRegisterMessage message, + TimeSpan timeout) + { + return await RegisterAsync(message, timeout, CancellationToken.None).ConfigureAwait(false); + } + /// /// Registers a device described by the message. /// @@ -52,6 +67,22 @@ public ProvisioningTransportHandlerAmqp( public override async Task RegisterAsync( ProvisioningTransportRegisterMessage message, CancellationToken cancellationToken) + { + return await RegisterAsync(message, s_timeoutConstant, cancellationToken).ConfigureAwait(false); + } + + /// + /// Registers a device described by the message. Because the AMQP library does not accept cancellation tokens, the provided cancellation token + /// will only be checked for cancellation between AMQP operations. The timeout will be respected during the AMQP operations. + /// + /// The provisioning message. + /// The maximum amount of time to allow this operation to run for before timing out. + /// The cancellation token. + /// The registration result. + private async Task RegisterAsync( + ProvisioningTransportRegisterMessage message, + TimeSpan timeout, + CancellationToken cancellationToken) { if (Logging.IsEnabled) { @@ -106,19 +137,21 @@ public override async Task RegisterAsync( string linkEndpoint = $"{message.IdScope}/registrations/{registrationId}"; using AmqpClientConnection connection = authStrategy.CreateConnection(builder.Uri, message.IdScope); - await authStrategy.OpenConnectionAsync(connection, s_timeoutConstant, useWebSocket, Proxy, RemoteCertificateValidationCallback).ConfigureAwait(false); + await authStrategy.OpenConnectionAsync(connection, timeout, useWebSocket, Proxy, RemoteCertificateValidationCallback).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await CreateLinksAsync( connection, linkEndpoint, - message.ProductInfo).ConfigureAwait(false); + message.ProductInfo, + timeout).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); string correlationId = Guid.NewGuid().ToString(); DeviceRegistration deviceRegistration = (message.Payload != null && message.Payload.Length > 0) ? new DeviceRegistration { Payload = new JRaw(message.Payload) } : null; - RegistrationOperationStatus operation = await RegisterDeviceAsync(connection, correlationId, deviceRegistration).ConfigureAwait(false); + RegistrationOperationStatus operation = await RegisterDeviceAsync(connection, correlationId, deviceRegistration, timeout).ConfigureAwait(false); // Poll with operationId until registration complete. int attempts = 0; @@ -131,7 +164,7 @@ await CreateLinksAsync( cancellationToken.ThrowIfCancellationRequested(); await Task.Delay( - operation.RetryAfter ?? RetryJitter.GenerateDelayWithJitterForRetry(s_defaultOperationPoolingInterval), + operation.RetryAfter ?? RetryJitter.GenerateDelayWithJitterForRetry(s_defaultOperationPollingInterval), cancellationToken).ConfigureAwait(false); try @@ -139,7 +172,8 @@ await Task.Delay( operation = await OperationStatusLookupAsync( connection, operationId, - correlationId).ConfigureAwait(false); + correlationId, + timeout).ConfigureAwait(false); } catch (ProvisioningTransportException e) when (e.ErrorDetails is ProvisioningErrorDetailsAmqp amqp && e.IsTransient) { @@ -154,7 +188,7 @@ await Task.Delay( authStrategy.SaveCredentials(operation); } - await connection.CloseAsync(s_timeoutConstant).ConfigureAwait(false); + await connection.CloseAsync(timeout).ConfigureAwait(false); return ConvertToProvisioningRegistrationResult(operation.RegistrationState); } @@ -179,30 +213,31 @@ await Task.Delay( } } - private static async Task CreateLinksAsync(AmqpClientConnection connection, string linkEndpoint, string productInfo) + private static async Task CreateLinksAsync(AmqpClientConnection connection, string linkEndpoint, string productInfo, TimeSpan timeout) { AmqpClientSession amqpDeviceSession = connection.CreateSession(); - await amqpDeviceSession.OpenAsync(s_timeoutConstant).ConfigureAwait(false); + await amqpDeviceSession.OpenAsync(timeout).ConfigureAwait(false); AmqpClientLink amqpReceivingLink = amqpDeviceSession.CreateReceivingLink(linkEndpoint); amqpReceivingLink.AddClientVersion(productInfo); amqpReceivingLink.AddApiVersion(ClientApiVersionHelper.ApiVersion); - await amqpReceivingLink.OpenAsync(s_timeoutConstant).ConfigureAwait(false); + await amqpReceivingLink.OpenAsync(timeout).ConfigureAwait(false); AmqpClientLink amqpSendingLink = amqpDeviceSession.CreateSendingLink(linkEndpoint); amqpSendingLink.AddClientVersion(productInfo); amqpSendingLink.AddApiVersion(ClientApiVersionHelper.ApiVersion); - await amqpSendingLink.OpenAsync(s_timeoutConstant).ConfigureAwait(false); + await amqpSendingLink.OpenAsync(timeout).ConfigureAwait(false); } private async Task RegisterDeviceAsync( AmqpClientConnection client, string correlationId, - DeviceRegistration deviceRegistration) + DeviceRegistration deviceRegistration, + TimeSpan timeout) { AmqpMessage amqpMessage = null; @@ -226,11 +261,11 @@ private async Task RegisterDeviceAsync( .SendMessageAsync( amqpMessage, new ArraySegment(Guid.NewGuid().ToByteArray()), - s_timeoutConstant) + timeout) .ConfigureAwait(false); ValidateOutcome(outcome); - AmqpMessage amqpResponse = await client.AmqpSession.ReceivingLink.ReceiveMessageAsync(s_timeoutConstant).ConfigureAwait(false); + AmqpMessage amqpResponse = await client.AmqpSession.ReceivingLink.ReceiveMessageAsync(timeout).ConfigureAwait(false); client.AmqpSession.ReceivingLink.AcceptMessage(amqpResponse); using var streamReader = new StreamReader(amqpResponse.BodyStream); @@ -238,7 +273,7 @@ private async Task RegisterDeviceAsync( .ReadToEndAsync() .ConfigureAwait(false); RegistrationOperationStatus status = JsonConvert.DeserializeObject(jsonResponse); - status.RetryAfter = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultOperationPoolingInterval); + status.RetryAfter = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultOperationPollingInterval); return status; } finally @@ -250,7 +285,8 @@ private async Task RegisterDeviceAsync( private async Task OperationStatusLookupAsync( AmqpClientConnection client, string operationId, - string correlationId) + string correlationId, + TimeSpan timeout) { using var amqpMessage = AmqpMessage.Create(new AmqpValue { Value = DeviceOperations.GetOperationStatus }); @@ -263,12 +299,12 @@ private async Task OperationStatusLookupAsync( .SendMessageAsync( amqpMessage, new ArraySegment(Guid.NewGuid().ToByteArray()), - s_timeoutConstant) + timeout) .ConfigureAwait(false); ValidateOutcome(outcome); - AmqpMessage amqpResponse = await client.AmqpSession.ReceivingLink.ReceiveMessageAsync(s_timeoutConstant) + AmqpMessage amqpResponse = await client.AmqpSession.ReceivingLink.ReceiveMessageAsync(timeout) .ConfigureAwait(false); client.AmqpSession.ReceivingLink.AcceptMessage(amqpResponse); @@ -277,7 +313,7 @@ private async Task OperationStatusLookupAsync( string jsonResponse = await streamReader.ReadToEndAsync().ConfigureAwait(false); RegistrationOperationStatus status = JsonConvert.DeserializeObject(jsonResponse); - status.RetryAfter = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultOperationPoolingInterval); + status.RetryAfter = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultOperationPollingInterval); return status; } @@ -314,7 +350,7 @@ private void ValidateOutcome(Outcome outcome) bool isTransient = statusCode >= (int)HttpStatusCode.InternalServerError || statusCode == 429; if (isTransient) { - errorDetails.RetryAfter = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultOperationPoolingInterval); + errorDetails.RetryAfter = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultOperationPollingInterval); } throw new ProvisioningTransportException( diff --git a/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs b/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs index 59b3af6723..cdbd2bec00 100644 --- a/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs +++ b/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs @@ -32,6 +32,20 @@ public ProvisioningTransportHandlerHttp() Proxy = DefaultWebProxySettings.Instance; } + /// + /// Registers a device described by the message. + /// + /// The provisioning message. + /// The maximum amount of time to allow this operation to run for before timing out. + /// The registration result. + public override async Task RegisterAsync( + ProvisioningTransportRegisterMessage message, + TimeSpan timeout) + { + using var cts = new CancellationTokenSource(timeout); + return await RegisterAsync(message, cts.Token).ConfigureAwait(false); + } + /// /// Registers a device described by the message. /// diff --git a/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs b/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs index d62ee25bd4..99b596d641 100644 --- a/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs +++ b/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs @@ -63,6 +63,20 @@ public ProvisioningTransportHandlerMqtt( Proxy = DefaultWebProxySettings.Instance; } + /// + /// Registers a device described by the message. + /// + /// The provisioning message. + /// The maximum amount of time to allow this operation to run for before timing out. + /// The registration result. + public override async Task RegisterAsync( + ProvisioningTransportRegisterMessage message, + TimeSpan timeout) + { + using var cts = new CancellationTokenSource(timeout); + return await RegisterAsync(message, cts.Token).ConfigureAwait(false); + } + /// /// Registers a device described by the message. /// From 464d395c160049f235ac50c4144b62881e710f3d Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 21 Jun 2021 10:16:48 -0700 Subject: [PATCH 25/77] fix(iot-device): Update doc comments for MqttTransportSettings --- iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs index 020d9ecf41..99fe30cbd9 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs @@ -165,9 +165,9 @@ public bool CertificateRevocationCheck /// /// In the event that IoT Hub receives burst traffic, it will implement traffic shaping in order to process the incoming requests. /// In such cases, during client connection the CONNECT requests can have a delay in being acknowledged and processed by IoT Hub. - /// The governs the duration the client will wait for a CONNACK packet before disconnecting and reopening the connection. + /// The ConnectArrivalTimeout governs the duration the client will wait for a CONNACK packet before disconnecting and reopening the connection. /// To know more about IoT Hub's throttling limits and traffic shaping feature, see - /// . + /// . /// public TimeSpan ConnectArrivalTimeout { get; set; } From d46e0f07fe8d80e21e07b41c2e75b0bd1fcb8f80 Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Tue, 22 Jun 2021 13:56:02 -0700 Subject: [PATCH 26/77] Add documentation to repo about which platforms this SDK supports (#2046) fixes #2032 --- .github/ISSUE_TEMPLATE/bug_report.md | 2 + .github/ISSUE_TEMPLATE/feature_request.md | 2 + readme.md | 22 +-------- supported_platforms.md | 57 +++++++++++++++++++++++ vsts/vsts.yaml | 2 + 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 supported_platforms.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 32f03d8e6a..981ce363a2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -31,6 +31,8 @@ Please follow the instructions and template below to save us time requesting add Below is a generic bug report format. We recommend you use it as a template and replace the information below each header with your own. +Note that bugs that only affect unsupported platforms will likely be treated as feature requests, and may be closed as "won't fix" if we have no plans to support that platform. See [this document](../../supported_platforms.md) for details on which platforms are officially supported. + ------------------------------- delete above ------------------------------- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ba999c8c9c..cfc496eb27 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -26,6 +26,8 @@ Thank you for raising a feature request! To get started, here's a few things to As an open source project, we welcome PRs for new features, and we don't want to reject any hard work you've done to contribute. **So propose your solution through an issue first** so we can discuss the changes, and if things look good we will ask you submit a PR to implement the changes. +Note that feature requests that only affect unsupported platforms may be closed as "won't fix" if we have no plans to support that platform. See [this document](../../supported_platforms.md) for details on which platforms are officially supported. + ------------------------------- delete above ------------------------------- **Is your feature request related to a problem? Please describe.** diff --git a/readme.md b/readme.md index e0a58e09be..d001aed210 100644 --- a/readme.md +++ b/readme.md @@ -63,27 +63,9 @@ If you would like to build or change the SDK source code, please follow the [dev ## OS platforms and hardware compatibility -> .NET Standard 1.3 (IoT Hub SDKs only) is last supported in the [2020-02-27](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/2020-2-27) and in the [2020-1-31 LTS](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) releases. +For an official list of all the operating systems and .NET platforms that we support, please see [this document](./supported_platforms.md) -The IoT Hub device SDK for .NET can be used with a broad range of device platforms and is officially supported on the following Operating Systems: - -* Windows versions officially supported by Microsoft. -* [Linux distributions](https://docs.microsoft.com/en-us/dotnet/core/install/linux) supported by .NET core. - -> Note: For Linux, we test our clients against Ubuntu 16.04.7 LTS. - -The NuGet packages provide support for the following .NET flavors: -- .NET Standard 2.1 -- .NET Standard 2.0 -- .NET Framework 4.7.2 (IoT Hub SDKs only) -- .NET Framework 4.5.1 (IoT Hub SDKs only) - -For details on .NET support see the [.NET Standard documentation](https://docs.microsoft.com/dotnet/standard/net-standard). -For details on OS support see the following resources: - -- [.NET Core Runtime ID Catalog](https://docs.microsoft.com/dotnet/core/rid-catalog) -- [.NET Framework System Requirements](https://docs.microsoft.com/dotnet/framework/get-started/system-requirements) -- [Configure TLS Protocol Version and Ciphers](./configure_tls_protocol_version_and_ciphers.md) +Note that you can configure your TLS protocol version and ciphers by following [this document](./configure_tls_protocol_version_and_ciphers.md) ## Key features and roadmap diff --git a/supported_platforms.md b/supported_platforms.md new file mode 100644 index 0000000000..0944a25974 --- /dev/null +++ b/supported_platforms.md @@ -0,0 +1,57 @@ +# Microsoft Azure IoT SDKs for .NET + +This SDK is tested nightly on a mix of .NET implementations on both Windows 10 and on Ubuntu 1604. For additional details +for each tested platform, see the respective sections below. + +## Supported .NET versions + +The NuGet packages provide support for the following .NET versions: +- .NET Standard 2.1 +- .NET Standard 2.0 +- .NET Framework 4.7.2 (IoT Hub SDKs only) +- .NET Framework 4.5.1 (IoT Hub SDKs only) + +This SDK _may_ work with newer versions of .NET, but there are no guarantees that they will _always_ work for those until we officially add support for them. +versions nor are there guarantees that we will fix bugs that are only present on those versions. + +Note that applications will resolve the dll that is being referenced based on the framework precedence rules. This means that .NET Framework targeting applications will look for the closet .NET Framework dll. In the absence of that, it will pick up the closest .NET Standard dll. Similarly for netcoreapp applications will look for the closest netcoreapp dll and in the absence of one will pick the closest .NET Standard dll. Since we publish the above list of dlls, you should target the appropriate net target to ensure you get the desired .NET API coverage + +## Windows 10 + +Note that, while we only directly test on Windows 10, we do support other Windows versions officially supported by Microsoft. + +Nightly test platform details: + +.NET versions tested on +- .NET Core 3.1 +- .NET Core 2.1.18 +- .NET Framework 4.7.2 (only IoT Hub SDKs tested) +- .NET Framework 4.5.1 (only IoT Hub SDKs tested) + + +Default locale: en_US, platform encoding: Cp1252 + +OS name: "windows server 2016", version: "10.0", arch: "amd64", family: "windows" + +## Ubuntu 1604 + +Note that, while we only directly test on Ubuntu 1604, we do generally support other [Linux distributions supported by .NET core](https://docs.microsoft.com/en-us/dotnet/core/install/linux). + +Nightly test platform details: + +.NET versions tested on: +- .NET Core 3.1 +- .NET Core 2.1.18 + +Default locale: en_US, platform encoding: UTF-8 + +OS name: "linux", version: "4.15.0-1113-azure", arch: "amd64", family: "unix" + +## Miscellaneous support notes + +- This library has a [preview version](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/preview_2021-6-8) that supports .NET 5.0, but we don't officially support it in our main releases yet. +- This library does not officially support being run on MacOS. +- This library does not officially support being run in Xamarin applications. +- .NET Standard 1.3 (IoT Hub SDKs only) is last supported in the [2020-02-27](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/2020-2-27) and in the [2020-1-31 LTS](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) releases. +- [.NET Core Runtime ID Catalog](https://docs.microsoft.com/dotnet/core/rid-catalog) +- In order to run this SDK your device will need to meet the [.NET Framework System Requirements](https://docs.microsoft.com/dotnet/framework/get-started/system-requirements) diff --git a/vsts/vsts.yaml b/vsts/vsts.yaml index 99ecc9a4f2..c6415d2d18 100644 --- a/vsts/vsts.yaml +++ b/vsts/vsts.yaml @@ -32,6 +32,7 @@ jobs: condition: succeeded() pool: + # If this is changed, don't forget to update supported_platforms.md in the root directory. That document outlines what OS we test on and should stay up to date. name: Hosted Ubuntu 1604 steps: - task: Docker@1 @@ -141,6 +142,7 @@ jobs: condition: succeeded() pool: + # If this is changed, don't forget to update supported_platforms.md in the root directory. That document outlines what OS we test on and should stay up to date. name: Hosted VS2017 steps: - script: | From 6229acccd337983e01fac9fac9a8e62bd73d5d81 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 22 Jun 2021 17:04:15 -0700 Subject: [PATCH 27/77] readme: Update readme to have LTS patch reference --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index d001aed210..8ac4c066dd 100644 --- a/readme.md +++ b/readme.md @@ -180,6 +180,7 @@ Below is a table showing the mapping of the LTS branches to the packages release | Release | Github Branch | LTS Status | LTS Start Date | Maintenance End Date | LTS End Date | | :-------------------------------------------------------------------------------------------: | :-----------: | :--------: | :------------: | :------------------: | :----------: | +| [2021-6-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18_patch1) | lts_2021_03 | Active | 2020-03-18 | 2022-03-18 | 2024-03-17 | | [2021-3-18](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18) | lts_2021_03 | Active | 2020-03-18 | 2022-03-18 | 2024-03-17 | | [2020-9-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch1) | lts_2020_08 | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | | [2020-8-19](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19) | lts_2020_08 | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | From c83a1d51903f04ca3115ba5b906ced87ab3dec51 Mon Sep 17 00:00:00 2001 From: Sindhu Nagesh Date: Tue, 29 Jun 2021 09:50:34 -0700 Subject: [PATCH 28/77] doc(readme): Updated readme with details on support for device streaming feature. (#2054) --- readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/readme.md b/readme.md index 8ac4c066dd..d0ca4c0a96 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,15 @@ Due to security considerations, build logs are not publicly available. | Microsoft.Azure.Devices.DigitalTwin.Client | N/A | [![NuGet][pnp-device-prerelease]][pnp-device-nuget] | | Microsoft.Azure.Devices.DigitalTwin.Service | N/A | [![NuGet][pnp-service-prerelease]][pnp-service-nuget] | +> Note: +> Device streaming feature is not being included in our newer preview releases as there is no active development going on in the service. For more details on the feature, see [here](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-device-streams-overview). It is not recommended to take dependency on preview nugets for production applications as breaking changes can be introduced in preview nugets. +> +> The feature has not been included in any preview release after [2020-10-14](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/preview_2020-10-14). However, the feature is still available under previews/deviceStreaming branch. +> +> The latest preview nuget versions that contain the feature are: +Microsoft.Azure.Devices.Client - 1.32.0-preview-001 +Microsoft.Azure.Devices - 1.28.0-preview-001 + The API reference documentation for .NET SDK is [here][dotnet-api-reference]. To find SDKs in other languages for Azure IoT, please refer to the [azure-iot-sdks][azure-iot-sdks] repository. From a2a23d64acd05e4a2121fdc308d9a7ee8aa4f68b Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 29 Jun 2021 11:50:24 -0700 Subject: [PATCH 29/77] fix(iot-device): Update the amqp layer to inspect the inner exception for an amqp error code (#2053) --- .../iothub/messaging/MessageSendE2ETests.cs | 124 ++++++++++++++++-- .../AmqpIot/AmqpIotExceptionAdapter.cs | 21 ++- .../ProtocolRoutingDelegatingHandler.cs | 5 +- 3 files changed, 128 insertions(+), 22 deletions(-) diff --git a/e2e/test/iothub/messaging/MessageSendE2ETests.cs b/e2e/test/iothub/messaging/MessageSendE2ETests.cs index 974be9733e..7cca652d71 100644 --- a/e2e/test/iothub/messaging/MessageSendE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageSendE2ETests.cs @@ -20,10 +20,20 @@ namespace Microsoft.Azure.Devices.E2ETests.Messaging public partial class MessageSendE2ETests : E2EMsTestBase { private const int MessageBatchCount = 5; - private const int LargeMessageSizeInBytes = 255 * 1024; // The maximum message size for device to cloud messages is 256 KB. We are allowing 1 KB of buffer for message header information etc. - private readonly string DevicePrefix = $"{nameof(MessageSendE2ETests)}_"; - private readonly string ModulePrefix = $"{nameof(MessageSendE2ETests)}_"; - private static string ProxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + + // The maximum message size for device to cloud messages is 256 KB. We are allowing 1 KB of buffer for message header information etc. + private const int LargeMessageSizeInBytes = 255 * 1024; + + // The size of a device to cloud message. This exceeds the the maximum message size set by the hub; 256 KB. + private const int ExceedAllowedMessageSizeInBytes = 300 * 1024; + + // The size of a device to cloud message. This overly exceeds the maximum message size set by the hub, which is 256 KB. The reason why we are testing for this case is because + // we noticed a different behavior between this case, and the case where the message size is less than 1 MB. + private const int OverlyExceedAllowedMessageSizeInBytes = 3000 * 1024; + + private readonly string _devicePrefix = $"{nameof(MessageSendE2ETests)}_"; + private readonly string _modulePrefix = $"{nameof(MessageSendE2ETests)}_"; + private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; [LoggedTestMethod] public async Task Message_DeviceSendSingleMessage_Amqp() @@ -80,7 +90,7 @@ public async Task Message_DeviceSendSingleMessage_AmqpWs_WithHeartbeats() public async Task Message_DeviceSendSingleMessage_Http_WithProxy() { Client.Http1TransportSettings httpTransportSettings = new Client.Http1TransportSettings(); - httpTransportSettings.Proxy = new WebProxy(ProxyServerAddress); + httpTransportSettings.Proxy = new WebProxy(s_proxyServerAddress); ITransportSettings[] transportSettings = new ITransportSettings[] { httpTransportSettings }; await SendSingleMessage(TestDeviceType.Sasl, transportSettings).ConfigureAwait(false); @@ -105,7 +115,7 @@ public async Task Message_DeviceSendSingleMessage_Http_WithCustomProxy() public async Task Message_DeviceSendSingleMessage_AmqpWs_WithProxy() { Client.AmqpTransportSettings amqpTransportSettings = new Client.AmqpTransportSettings(Client.TransportType.Amqp_WebSocket_Only); - amqpTransportSettings.Proxy = new WebProxy(ProxyServerAddress); + amqpTransportSettings.Proxy = new WebProxy(s_proxyServerAddress); ITransportSettings[] transportSettings = new ITransportSettings[] { amqpTransportSettings }; await SendSingleMessage(TestDeviceType.Sasl, transportSettings).ConfigureAwait(false); @@ -117,7 +127,7 @@ public async Task Message_DeviceSendSingleMessage_MqttWs_WithProxy() { Client.Transport.Mqtt.MqttTransportSettings mqttTransportSettings = new Client.Transport.Mqtt.MqttTransportSettings(Client.TransportType.Mqtt_WebSocket_Only); - mqttTransportSettings.Proxy = new WebProxy(ProxyServerAddress); + mqttTransportSettings.Proxy = new WebProxy(s_proxyServerAddress); ITransportSettings[] transportSettings = new ITransportSettings[] { mqttTransportSettings }; await SendSingleMessage(TestDeviceType.Sasl, transportSettings).ConfigureAwait(false); @@ -128,7 +138,7 @@ public async Task Message_DeviceSendSingleMessage_MqttWs_WithProxy() public async Task Message_ModuleSendSingleMessage_AmqpWs_WithProxy() { Client.AmqpTransportSettings amqpTransportSettings = new Client.AmqpTransportSettings(Client.TransportType.Amqp_WebSocket_Only); - amqpTransportSettings.Proxy = new WebProxy(ProxyServerAddress); + amqpTransportSettings.Proxy = new WebProxy(s_proxyServerAddress); ITransportSettings[] transportSettings = new ITransportSettings[] { amqpTransportSettings }; await SendSingleMessageModule(transportSettings).ConfigureAwait(false); @@ -140,7 +150,7 @@ public async Task Message_ModuleSendSingleMessage_MqttWs_WithProxy() { Client.Transport.Mqtt.MqttTransportSettings mqttTransportSettings = new Client.Transport.Mqtt.MqttTransportSettings(Client.TransportType.Mqtt_WebSocket_Only); - mqttTransportSettings.Proxy = new WebProxy(ProxyServerAddress); + mqttTransportSettings.Proxy = new WebProxy(s_proxyServerAddress); ITransportSettings[] transportSettings = new ITransportSettings[] { mqttTransportSettings }; await SendSingleMessageModule(transportSettings).ConfigureAwait(false); @@ -212,7 +222,7 @@ public async Task X509_DeviceSendBatchMessages_Http() [ExpectedException(typeof(MessageTooLargeException))] public async Task Message_ClientThrowsForMqttTopicNameTooLong() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -245,9 +255,95 @@ public async Task Message_DeviceSendSingleLargeMessageAsync(TestDeviceType testD await SendSingleMessage(testDeviceType, transportType, messageSize).ConfigureAwait(false); } + [LoggedTestMethod] + [ExpectedException(typeof(MessageTooLargeException))] + public async Task Message_DeviceSendMessageOverAllowedSize_Amqp() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Amqp_Tcp_Only, ExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + [LoggedTestMethod] + [ExpectedException(typeof(MessageTooLargeException))] + public async Task Message_DeviceSendMessageOverAllowedSize_AmqpWs() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Amqp_WebSocket_Only, ExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + // MQTT protocol will throw an InvalidOperationException if the PUBLISH packet is greater than + // Hub limits: https://github.com/Azure/azure-iot-sdk-csharp/blob/d46e0f07fe8d80e21e07b41c2e75b0bd1fcb8f80/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs#L1175 + // This flow is a bit different from other protocols where we do not inspect the packet being sent but rather rely on service validating it + // and throwing a MessageTooLargeException, if relevant. + [LoggedTestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task Message_DeviceSendMessageOverAllowedSize_Mqtt() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Mqtt_Tcp_Only, ExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + // MQTT protocol will throw an InvalidOperationException if the PUBLISH packet is greater than + // Hub limits: https://github.com/Azure/azure-iot-sdk-csharp/blob/d46e0f07fe8d80e21e07b41c2e75b0bd1fcb8f80/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs#L1175 + // This flow is a bit different from other protocols where we do not inspect the packet being sent but rather rely on service validating it + // and throwing a MessageTooLargeException, if relevant. + [LoggedTestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task Message_DeviceSendMessageOverAllowedSize_MqttWs() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Mqtt_WebSocket_Only, ExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + [LoggedTestMethod] + [ExpectedException(typeof(MessageTooLargeException))] + public async Task Message_DeviceSendMessageOverAllowedSize_Http() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Http1, ExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + [LoggedTestMethod] + [ExpectedException(typeof(MessageTooLargeException))] + public async Task Message_DeviceSendMessageWayOverAllowedSize_Amqp() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Amqp_Tcp_Only, OverlyExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + [LoggedTestMethod] + [ExpectedException(typeof(MessageTooLargeException))] + public async Task Message_DeviceSendMessageWayOverAllowedSize_AmqpWs() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Amqp_WebSocket_Only, OverlyExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + // MQTT protocol will throw an InvalidOperationException if the PUBLISH packet is greater than + // Hub limits: https://github.com/Azure/azure-iot-sdk-csharp/blob/d46e0f07fe8d80e21e07b41c2e75b0bd1fcb8f80/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs#L1175 + // This flow is a bit different from other protocols where we do not inspect the packet being sent but rather rely on service validating it + // and throwing a MessageTooLargeException, if relevant. + [LoggedTestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task Message_DeviceSendMessageWayOverAllowedSize_Mqtt() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Mqtt_Tcp_Only, OverlyExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + // MQTT protocol will throw an InvalidOperationException if the PUBLISH packet is greater than + // Hub limits: https://github.com/Azure/azure-iot-sdk-csharp/blob/d46e0f07fe8d80e21e07b41c2e75b0bd1fcb8f80/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs#L1175 + // This flow is a bit different from other protocols where we do not inspect the packet being sent but rather rely on service validating it + // and throwing a MessageTooLargeException, if relevant. + [LoggedTestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task Message_DeviceSendMessageWayOverAllowedSize_MqttWs() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Mqtt_WebSocket_Only, OverlyExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + + [LoggedTestMethod] + [ExpectedException(typeof(MessageTooLargeException))] + public async Task Message_DeviceSendMessageWayOverAllowedSize_Http() + { + await SendSingleMessage(TestDeviceType.Sasl, Client.TransportType.Http1, OverlyExceedAllowedMessageSizeInBytes).ConfigureAwait(false); + } + private async Task SendSingleMessage(TestDeviceType type, Client.TransportType transport, int messageSize = 0) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, type).ConfigureAwait(false); + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -257,7 +353,7 @@ private async Task SendSingleMessage(TestDeviceType type, Client.TransportType t private async Task SendBatchMessages(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, type).ConfigureAwait(false); + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -267,7 +363,7 @@ private async Task SendBatchMessages(TestDeviceType type, Client.TransportType t private async Task SendSingleMessage(TestDeviceType type, ITransportSettings[] transportSettings, int messageSize = 0) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, type).ConfigureAwait(false); + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -277,7 +373,7 @@ private async Task SendSingleMessage(TestDeviceType type, ITransportSettings[] t private async Task SendSingleMessageModule(ITransportSettings[] transportSettings) { - TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); + TestModule testModule = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); using var moduleClient = ModuleClient.CreateFromConnectionString(testModule.ConnectionString, transportSettings); await moduleClient.OpenAsync().ConfigureAwait(false); diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotExceptionAdapter.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotExceptionAdapter.cs index fff5e23334..57babf2e13 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotExceptionAdapter.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotExceptionAdapter.cs @@ -15,17 +15,26 @@ internal static Exception ConvertToIotHubException(Exception exception) { return new IotHubCommunicationException(exception.Message, exception); } - else if (exception is UnauthorizedAccessException) + + if (exception is UnauthorizedAccessException) { return new UnauthorizedException(exception.Message, exception); } - else + + if (exception is OperationCanceledException + && exception.InnerException is AmqpException innerAmqpException + && innerAmqpException != null) { - var amqpException = exception as AmqpException; - return amqpException == null - ? exception - : AmqpIotErrorAdapter.ToIotHubClientContract(amqpException); + return AmqpIotErrorAdapter.ToIotHubClientContract(innerAmqpException); } + + if (exception is AmqpException amqpException + && amqpException != null) + { + return AmqpIotErrorAdapter.ToIotHubClientContract(amqpException); + } + + return exception; } internal static Exception ConvertToIotHubException(Exception exception, AmqpObject source) diff --git a/iothub/device/src/Transport/ProtocolRoutingDelegatingHandler.cs b/iothub/device/src/Transport/ProtocolRoutingDelegatingHandler.cs index d0b624cd8e..20636f8ef3 100644 --- a/iothub/device/src/Transport/ProtocolRoutingDelegatingHandler.cs +++ b/iothub/device/src/Transport/ProtocolRoutingDelegatingHandler.cs @@ -10,8 +10,8 @@ namespace Microsoft.Azure.Devices.Client.Transport { /// - /// Transport handler router. - /// Tries to open the connection in the protocol order it was set. + /// Transport handler router. + /// Tries to open the connection in the protocol order it was set. /// If fails tries to open the next one, etc. /// internal class ProtocolRoutingDelegatingHandler : DefaultDelegatingHandler @@ -22,6 +22,7 @@ internal class ProtocolRoutingDelegatingHandler : DefaultDelegatingHandler /// After we've verified that we could open the transport for any operation, we will stop attempting others in the list. /// private bool _transportSelectionComplete; + private int _nextTransportIndex; private SemaphoreSlim _handlerLock = new SemaphoreSlim(1, 1); From be4389016cb33cb3efc4f47bfc846880d2d2dd20 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 30 Jun 2021 17:14:45 -0700 Subject: [PATCH 30/77] refactor(samples): Move preview samples to samples repository (#2057) --- azureiot.sln | 17 - .../Models/DeviceInformation.json | 64 -- .../Models/TemperatureController.json | 83 --- .../Models/Thermostat.json | 89 --- .../Models/Thermostat2.json | 89 --- .../TemperatureController/Parameter.cs | 86 --- .../TemperatureController/Program.cs | 165 ----- .../Properties/launchSettings.template.json | 22 - .../SystemTextJsonPayloadConvention.cs | 19 - .../SystemTextJsonPayloadSerializer.cs | 67 -- .../SystemTextJsonWritablePropertyResponse.cs | 57 -- .../TemperatureController.csproj | 19 - .../TemperatureControllerSample.cs | 412 ------------- .../TemperatureReport.cs | 26 - .../Thermostat/Models/Thermostat.json | 89 --- .../Thermostat/Parameter.cs | 86 --- .../Thermostat/Program.cs | 162 ----- .../Properties/launchSettings.template.json | 22 - .../Thermostat/TemperatureReport.cs | 26 - .../Thermostat/Thermostat.csproj | 19 - .../Thermostat/ThermostatSample.cs | 205 ------- .../convention-based-samples/readme.md | 577 ------------------ iothub/device/samples/readme.md | 35 +- 23 files changed, 19 insertions(+), 2417 deletions(-) delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Program.cs delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs delete mode 100644 iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Program.cs delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj delete mode 100644 iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs delete mode 100644 iothub/device/samples/convention-based-samples/readme.md diff --git a/azureiot.sln b/azureiot.sln index 5b4d4d37ae..3b894997b8 100644 --- a/azureiot.sln +++ b/azureiot.sln @@ -79,12 +79,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Sha EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{2368415A-9C09-4F47-9636-FDCA4B85C88C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "convention-based-samples", "convention-based-samples", "{22318FE4-1453-41BF-A38D-9401C4F16023}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thermostat", "iothub\device\samples\convention-based-samples\Thermostat\Thermostat.csproj", "{5658A5DF-EDEF-4561-9F0B-A37EEABC8135}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemperatureController", "iothub\device\samples\convention-based-samples\TemperatureController\TemperatureController.csproj", "{B557FCFE-015C-4A65-81B6-B4987E07BFB7}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -158,14 +152,6 @@ Global {CEEE435F-32FC-4DE5-8735-90F6AC950A01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CEEE435F-32FC-4DE5-8735-90F6AC950A01}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEEE435F-32FC-4DE5-8735-90F6AC950A01}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5658A5DF-EDEF-4561-9F0B-A37EEABC8135}.Release|Any CPU.Build.0 = Release|Any CPU - {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B557FCFE-015C-4A65-81B6-B4987E07BFB7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -201,9 +187,6 @@ Global {8E25CDE3-992D-4942-8C38-51A0D8E8EB70} = {9C260BF0-1CCA-45A2-AAB8-6419291B8B88} {CEEE435F-32FC-4DE5-8735-90F6AC950A01} = {3AA089A9-A035-439E-BAF6-C3975A334379} {2368415A-9C09-4F47-9636-FDCA4B85C88C} = {A48437BA-3C5B-431E-9B2F-96C850E9E1A5} - {22318FE4-1453-41BF-A38D-9401C4F16023} = {2368415A-9C09-4F47-9636-FDCA4B85C88C} - {5658A5DF-EDEF-4561-9F0B-A37EEABC8135} = {22318FE4-1453-41BF-A38D-9401C4F16023} - {B557FCFE-015C-4A65-81B6-B4987E07BFB7} = {22318FE4-1453-41BF-A38D-9401C4F16023} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF61665D-340A-494B-9705-571456BDC752} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json deleted file mode 100644 index 6d59180dc8..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Models/DeviceInformation.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "@id": "dtmi:azure:DeviceManagement:DeviceInformation;1", - "@type": "Interface", - "displayName": "Device Information", - "contents": [ - { - "@type": "Property", - "name": "manufacturer", - "displayName": "Manufacturer", - "schema": "string", - "description": "Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso." - }, - { - "@type": "Property", - "name": "model", - "displayName": "Device model", - "schema": "string", - "description": "Device model name or ID. Ex. Surface Book 2." - }, - { - "@type": "Property", - "name": "swVersion", - "displayName": "Software version", - "schema": "string", - "description": "Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45" - }, - { - "@type": "Property", - "name": "osName", - "displayName": "Operating system name", - "schema": "string", - "description": "Name of the operating system on the device. Ex. Windows 10 IoT Core." - }, - { - "@type": "Property", - "name": "processorArchitecture", - "displayName": "Processor architecture", - "schema": "string", - "description": "Architecture of the processor on the device. Ex. x64 or ARM." - }, - { - "@type": "Property", - "name": "processorManufacturer", - "displayName": "Processor manufacturer", - "schema": "string", - "description": "Name of the manufacturer of the processor on the device. Ex. Intel." - }, - { - "@type": "Property", - "name": "totalStorage", - "displayName": "Total storage", - "schema": "double", - "description": "Total available storage on the device in kilobytes. Ex. 2048000 kilobytes." - }, - { - "@type": "Property", - "name": "totalMemory", - "displayName": "Total memory", - "schema": "double", - "description": "Total available memory on the device in kilobytes. Ex. 256000 kilobytes." - } - ], - "@context": "dtmi:dtdl:context;2" -} \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json deleted file mode 100644 index 997b5cd34a..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Models/TemperatureController.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "@context": [ - "dtmi:iotcentral:context;2", - "dtmi:dtdl:context;2" - ], - "@id": "dtmi:com:example:TemperatureController;2", - "@type": "Interface", - "contents": [ - { - "@type": [ - "Telemetry", - "DataSize" - ], - "description": { - "en": "Current working set of the device memory in KiB." - }, - "displayName": { - "en": "Working Set" - }, - "name": "workingSet", - "schema": "double", - "unit": "kibibit" - }, - { - "@type": "Property", - "displayName": { - "en": "Serial Number" - }, - "name": "serialNumber", - "schema": "string", - "writable": false - }, - { - "@type": "Command", - "commandType": "synchronous", - "description": { - "en": "Reboots the device after waiting the number of seconds specified." - }, - "displayName": { - "en": "Reboot" - }, - "name": "reboot", - "request": { - "@type": "CommandPayload", - "description": { - "en": "Number of seconds to wait before rebooting the device." - }, - "displayName": { - "en": "Delay" - }, - "name": "delay", - "schema": "integer" - } - }, - { - "@type": "Component", - "displayName": { - "en": "thermostat1" - }, - "name": "thermostat1", - "schema": "dtmi:com:example:Thermostat;1" - }, - { - "@type": "Component", - "displayName": { - "en": "thermostat2" - }, - "name": "thermostat2", - "schema": "dtmi:com:example:Thermostat;2" - }, - { - "@type": "Component", - "displayName": { - "en": "DeviceInfo" - }, - "name": "deviceInformation", - "schema": "dtmi:azure:DeviceManagement:DeviceInformation;1" - } - ], - "displayName": { - "en": "Temperature Controller" - } - } \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json deleted file mode 100644 index 46b21f85cb..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "@context": "dtmi:dtdl:context;2", - "@id": "dtmi:com:example:Thermostat;1", - "@type": "Interface", - "displayName": "Thermostat", - "description": "Reports current temperature and provides desired temperature control.", - "contents": [ - { - "@type": [ - "Telemetry", - "Temperature" - ], - "name": "temperature", - "displayName" : "Temperature", - "description" : "Temperature in degrees Celsius.", - "schema": "double", - "unit": "degreeCelsius" - }, - { - "@type": [ - "Property", - "Temperature" - ], - "name": "targetTemperature", - "schema": "double", - "displayName": "Target Temperature", - "description": "Allows to remotely specify the desired target temperature.", - "unit" : "degreeCelsius", - "writable": true - }, - { - "@type": [ - "Property", - "Temperature" - ], - "name": "maxTempSinceLastReboot", - "schema": "double", - "unit" : "degreeCelsius", - "displayName": "Max temperature since last reboot.", - "description": "Returns the max temperature since last device reboot." - }, - { - "@type": "Command", - "name": "getMaxMinReport", - "displayName": "Get Max-Min report.", - "description": "This command returns the max, min and average temperature from the specified time to the current time.", - "request": { - "name": "since", - "displayName": "Since", - "description": "Period to return the max-min report.", - "schema": "dateTime" - }, - "response": { - "name" : "tempReport", - "displayName": "Temperature Report", - "schema": { - "@type": "Object", - "fields": [ - { - "name": "maxTemp", - "displayName": "Max temperature", - "schema": "double" - }, - { - "name": "minTemp", - "displayName": "Min temperature", - "schema": "double" - }, - { - "name" : "avgTemp", - "displayName": "Average Temperature", - "schema": "double" - }, - { - "name" : "startTime", - "displayName": "Start Time", - "schema": "dateTime" - }, - { - "name" : "endTime", - "displayName": "End Time", - "schema": "dateTime" - } - ] - } - } - } - ] -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json b/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json deleted file mode 100644 index 2309b4a5d4..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Models/Thermostat2.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "@context": "dtmi:dtdl:context;2", - "@id": "dtmi:com:example:Thermostat;2", - "@type": "Interface", - "displayName": "Thermostat", - "description": "Reports current temperature and provides desired temperature control.", - "contents": [ - { - "@type": [ - "Telemetry", - "Temperature" - ], - "name": "temperature", - "displayName" : "Temperature", - "description" : "Temperature in degrees Celsius.", - "schema": "double", - "unit": "degreeCelsius" - }, - { - "@type": [ - "Property", - "Temperature" - ], - "name": "targetTemperature", - "schema": "double", - "displayName": "Target Temperature", - "description": "Allows to remotely specify the desired target temperature.", - "unit" : "degreeCelsius", - "writable": true - }, - { - "@type": [ - "Property", - "Temperature" - ], - "name": "maxTempSinceLastReboot", - "schema": "double", - "unit" : "degreeCelsius", - "displayName": "Max temperature since last reboot.", - "description": "Returns the max temperature since last device reboot." - }, - { - "@type": "Command", - "name": "getMaxMinReport", - "displayName": "Get Max-Min report.", - "description": "This command returns the max, min and average temperature from the specified time to the current time.", - "request": { - "name": "since", - "displayName": "Since", - "description": "Period to return the max-min report.", - "schema": "dateTime" - }, - "response": { - "name" : "tempReport", - "displayName": "Temperature Report", - "schema": { - "@type": "Object", - "fields": [ - { - "name": "maxTemp", - "displayName": "Max temperature", - "schema": "double" - }, - { - "name": "minTemp", - "displayName": "Min temperature", - "schema": "double" - }, - { - "name" : "avgTemp", - "displayName": "Average Temperature", - "schema": "double" - }, - { - "name" : "startTime", - "displayName": "Start Time", - "schema": "dateTime" - }, - { - "name" : "endTime", - "displayName": "End Time", - "schema": "dateTime" - } - ] - } - } - } - ] -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs b/iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs deleted file mode 100644 index 9f8f5b99ff..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Parameter.cs +++ /dev/null @@ -1,86 +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 CommandLine; -using Microsoft.Extensions.Logging; -using System; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - /// - /// Parameters for the application supplied via command line arguments. - /// If the parameter is not supplied via command line args, it will look for it in environment variables. - /// - internal class Parameters - { - [Option( - "DeviceSecurityType", - HelpText = "(Required) The flow that will be used for connecting the device for the sample. Possible case-insensitive values include: dps, connectionString." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\".")] - public string DeviceSecurityType { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_SECURITY_TYPE"); - - [Option( - 'p', - "PrimaryConnectionString", - HelpText = "(Required if DeviceSecurityType is \"connectionString\"). \nThe primary connection string for the device to simulate." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_CONNECTION_STRING\".")] - public string PrimaryConnectionString { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_CONNECTION_STRING"); - - [Option( - 'e', - "DpsEndpoint", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS endpoint to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ENDPOINT\".")] - public string DpsEndpoint { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ENDPOINT"); - - [Option( - 'i', - "DpsIdScope", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS ID Scope to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ID_SCOPE\".")] - public string DpsIdScope { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ID_SCOPE"); - - [Option( - 'd', - "DeviceId", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device registration Id to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_ID\".")] - public string DeviceId { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_ID"); - - [Option( - 'k', - "DeviceSymmetricKey", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device symmetric key to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_KEY\".")] - public string DeviceSymmetricKey { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_KEY"); - - [Option( - 'r', - "Application running time (in seconds)", - Required = false, - HelpText = "The running time for this console application. Leave it unassigned to run the application until it is explicitly canceled using Control+C.")] - public double? ApplicationRunningTime { get; set; } - - public bool Validate(ILogger logger) - { - if (string.IsNullOrWhiteSpace(DeviceSecurityType)) - { - logger.LogWarning("Device provisioning type not set, please set the environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\"" + - "or pass in \"-s | --DeviceSecurityType\" through command line. \nWill default to using \"dps\" flow."); - - DeviceSecurityType = "dps"; - } - - return (DeviceSecurityType.ToLowerInvariant()) switch - { - "dps" => !string.IsNullOrWhiteSpace(DpsEndpoint) - && !string.IsNullOrWhiteSpace(DpsIdScope) - && !string.IsNullOrWhiteSpace(DeviceId) - && !string.IsNullOrWhiteSpace(DeviceSymmetricKey), - "connectionstring" => !string.IsNullOrWhiteSpace(PrimaryConnectionString), - _ => throw new ArgumentException($"Unrecognized value for device provisioning received: {DeviceSecurityType}." + - $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."), - }; - } - } -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs b/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs deleted file mode 100644 index e2fcc0ba2e..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Program.cs +++ /dev/null @@ -1,165 +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 CommandLine; -using Microsoft.Azure.Devices.Provisioning.Client; -using Microsoft.Azure.Devices.Provisioning.Client.PlugAndPlay; -using Microsoft.Azure.Devices.Provisioning.Client.Transport; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - public class Program - { - // DTDL interface used: https://github.com/Azure/iot-plugandplay-models/blob/main/dtmi/com/example/temperaturecontroller-2.json - // The TemperatureController model contains 2 Thermostat components that implement different versions of Thermostat models. - // Both Thermostat models are identical in definition but this is done to allow IoT Central to handle - // TemperatureController model correctly. - private const string ModelId = "dtmi:com:example:TemperatureController;2"; - - private static ILogger s_logger; - - public static async Task Main(string[] args) - { - // Parse application parameters - Parameters parameters = null; - ParserResult result = Parser.Default.ParseArguments(args) - .WithParsed(parsedParams => - { - parameters = parsedParams; - }) - .WithNotParsed(errors => - { - Environment.Exit(1); - }); - - s_logger = InitializeConsoleDebugLogger(); - if (!parameters.Validate(s_logger)) - { - throw new ArgumentException("Required parameters are not set. Please recheck required variables by using \"--help\""); - } - - var runningTime = parameters.ApplicationRunningTime != null - ? TimeSpan.FromSeconds((double)parameters.ApplicationRunningTime) - : Timeout.InfiniteTimeSpan; - - s_logger.LogInformation("Press Control+C to quit the sample."); - using var cts = new CancellationTokenSource(runningTime); - Console.CancelKeyPress += (sender, eventArgs) => - { - eventArgs.Cancel = true; - cts.Cancel(); - s_logger.LogInformation("Sample execution cancellation requested; will exit."); - }; - - s_logger.LogDebug($"Set up the device client."); - using DeviceClient deviceClient = await SetupDeviceClientAsync(parameters, cts.Token); - var sample = new TemperatureControllerSample(deviceClient, s_logger); - await sample.PerformOperationsAsync(cts.Token); - - // PerformOperationsAsync is designed to run until cancellation has been explicitly requested, either through - // cancellation token expiration or by Console.CancelKeyPress. - // As a result, by the time the control reaches the call to close the device client, the cancellation token source would - // have already had cancellation requested. - // Hence, if you want to pass a cancellation token to any subsequent calls, a new token needs to be generated. - // For device client APIs, you can also call them without a cancellation token, which will set a default - // cancellation timeout of 4 minutes: https://github.com/Azure/azure-iot-sdk-csharp/blob/64f6e9f24371bc40ab3ec7a8b8accbfb537f0fe1/iothub/device/src/InternalClient.cs#L1922 - await deviceClient.CloseAsync(); - } - - private static ILogger InitializeConsoleDebugLogger() - { - ILoggerFactory loggerFactory = LoggerFactory.Create(builder => - { - builder - .AddFilter(level => level >= LogLevel.Debug) - .AddConsole(options => - { - options.TimestampFormat = "[MM/dd/yyyy HH:mm:ss]"; - }); - }); - - return loggerFactory.CreateLogger(); - } - - private static async Task SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken) - { - DeviceClient deviceClient; - switch (parameters.DeviceSecurityType.ToLowerInvariant()) - { - case "dps": - s_logger.LogDebug($"Initializing via DPS"); - DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken); - var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey); - deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod); - break; - - case "connectionstring": - s_logger.LogDebug($"Initializing via IoT Hub connection string"); - deviceClient = InitializeDeviceClient(parameters.PrimaryConnectionString); - break; - - default: - throw new ArgumentException($"Unrecognized value for device provisioning received: {parameters.DeviceSecurityType}." + - $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."); - } - return deviceClient; - } - - // Provision a device via DPS, by sending the PnP model Id as DPS payload. - private static async Task ProvisionDeviceAsync(Parameters parameters, CancellationToken cancellationToken) - { - SecurityProvider symmetricKeyProvider = new SecurityProviderSymmetricKey(parameters.DeviceId, parameters.DeviceSymmetricKey, null); - ProvisioningTransportHandler mqttTransportHandler = new ProvisioningTransportHandlerMqtt(); - ProvisioningDeviceClient pdc = ProvisioningDeviceClient.Create(parameters.DpsEndpoint, parameters.DpsIdScope, symmetricKeyProvider, mqttTransportHandler); - - var pnpPayload = new ProvisioningRegistrationAdditionalData - { - JsonData = PnpConvention.CreateDpsPayload(ModelId), - }; - return await pdc.RegisterAsync(pnpPayload, cancellationToken); - } - - // Initialize the device client instance using connection string based authentication, over Mqtt protocol (TCP, with fallback over Websocket) and - // setting the ModelId into ClientOptions.This method also sets a connection status change callback, that will get triggered any time the device's - // connection status changes. - private static DeviceClient InitializeDeviceClient(string deviceConnectionString) - { - // Specify a custom System.Text.Json based PayloadConvention to be used. - var options = new ClientOptions(SystemTextJsonPayloadConvention.Instance) - { - ModelId = ModelId, - }; - - DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); - deviceClient.SetConnectionStatusChangesHandler((status, reason) => - { - s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); - }); - - return deviceClient; - } - - // Initialize the device client instance using symmetric key based authentication, over Mqtt protocol (TCP, with fallback over Websocket) - // and setting the ModelId into ClientOptions. This method also sets a connection status change callback, that will get triggered any time the device's connection status changes. - private static DeviceClient InitializeDeviceClient(string hostname, IAuthenticationMethod authenticationMethod) - { - var options = new ClientOptions - { - ModelId = ModelId, - }; - - DeviceClient deviceClient = DeviceClient.Create(hostname, authenticationMethod, TransportType.Mqtt, options); - deviceClient.SetConnectionStatusChangesHandler((status, reason) => - { - s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); - }); - - return deviceClient; - } - } -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json b/iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json deleted file mode 100644 index 6ae8d6e736..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/Properties/launchSettings.template.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "profiles": { - "Hub": { - "commandName": "Project", - "environmentVariables": { - "IOTHUB_DEVICE_SECURITY_TYPE": "connectionString", - "IOTHUB_DEVICE_CONNECTION_STRING": "" - } - }, - "DPS": { - "commandName": "Project", - "environmentVariables": { - "IOTHUB_DEVICE_SECURITY_TYPE": "dps", - "IOTHUB_DEVICE_DPS_ID_SCOPE": "", - "IOTHUB_DEVICE_DPS_DEVICE_ID": "", - "IOTHUB_DEVICE_DPS_DEVICE_KEY": "", - "IOTHUB_DEVICE_DPS_ENDPOINT": "global.azure-devices-provisioning.net" - }, - "sqlDebugging": false - } - } -} \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs deleted file mode 100644 index 02cced0002..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadConvention.cs +++ /dev/null @@ -1,19 +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 Microsoft.Azure.Devices.Shared; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - /// - /// A that uses . - /// - public class SystemTextJsonPayloadConvention : PayloadConvention - { - public static readonly SystemTextJsonPayloadConvention Instance = new SystemTextJsonPayloadConvention(); - - public override PayloadSerializer PayloadSerializer { get; } = SystemTextJsonPayloadSerializer.Instance; - - public override PayloadEncoder PayloadEncoder { get; } = Utf8PayloadEncoder.Instance; - } -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs deleted file mode 100644 index c3cad86935..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonPayloadSerializer.cs +++ /dev/null @@ -1,67 +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.Text.Json; -using Microsoft.Azure.Devices.Shared; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - /// - /// A implementation. - /// - public class SystemTextJsonPayloadSerializer : PayloadSerializer - { - /// - /// The Content Type string. - /// - internal const string ApplicationJson = "application/json"; - - /// - /// The default instance of this class. - /// - public static readonly SystemTextJsonPayloadSerializer Instance = new SystemTextJsonPayloadSerializer(); - - /// - public override string ContentType => ApplicationJson; - - /// - public override string SerializeToString(object objectToSerialize) - { - return JsonSerializer.Serialize(objectToSerialize); - } - - /// - public override T DeserializeToType(string stringToDeserialize) - { - return JsonSerializer.Deserialize(stringToDeserialize); - } - - /// - public override T ConvertFromObject(object objectToConvert) - { - return DeserializeToType(SerializeToString(objectToConvert)); - } - - /// - public override bool TryGetNestedObjectValue(object nestedObject, string propertyName, out T outValue) - { - outValue = default; - if (nestedObject == null || string.IsNullOrEmpty(propertyName)) - { - return false; - } - if (((JsonElement)nestedObject).TryGetProperty(propertyName, out JsonElement element)) - { - outValue = DeserializeToType(element.GetRawText()); - return true; - } - return false; - } - - /// - public override IWritablePropertyResponse CreateWritablePropertyResponse(object value, int statusCode, long version, string description = null) - { - return new SystemTextJsonWritablePropertyResponse(value, statusCode, version, description); - } - } -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs b/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs deleted file mode 100644 index 97a8a84d64..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/SystemTextJsonWritablePropertyResponse.cs +++ /dev/null @@ -1,57 +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.Text.Json.Serialization; -using Microsoft.Azure.Devices.Shared; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - /// - /// An optional, helper class for constructing a writable property response. - /// - /// - /// This helper class will only work with . - /// It uses based to define the JSON property names. - /// - public sealed class SystemTextJsonWritablePropertyResponse : IWritablePropertyResponse - { - /// - /// Convenience constructor for specifying the properties. - /// - /// The unserialized property value. - /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. - /// The acknowledgment version, as supplied in the property update request. - /// The acknowledgment description, an optional, human-readable message about the result of the property update. - public SystemTextJsonWritablePropertyResponse(object value, int ackCode, long ackVersion, string ackDescription = default) - { - Value = value; - AckCode = ackCode; - AckVersion = ackVersion; - AckDescription = ackDescription; - } - - /// - /// The unserialized property value. - /// - [JsonPropertyName(ConventionBasedConstants.ValuePropertyName)] - public object Value { get; set; } - - /// - /// The acknowledgment code, usually an HTTP Status Code e.g. 200, 400. - /// - [JsonPropertyName(ConventionBasedConstants.AckCodePropertyName)] - public int AckCode { get; set; } - - /// - /// The acknowledgment version, as supplied in the property update request. - /// - [JsonPropertyName(ConventionBasedConstants.AckVersionPropertyName)] - public long AckVersion { get; set; } - - /// - /// The acknowledgment description, an optional, human-readable message about the result of the property update. - /// - [JsonPropertyName(ConventionBasedConstants.AckDescriptionPropertyName)] - public string AckDescription { get; set; } - } -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj deleted file mode 100644 index d0fbcbc34a..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureController.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - netcoreapp3.1 - $(MSBuildProjectDirectory)\..\..\..\..\.. - - - - - - - - - - - - - diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs deleted file mode 100644 index 041adeffb3..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureControllerSample.cs +++ /dev/null @@ -1,412 +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.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - public class TemperatureControllerSample - { - private const string Thermostat1 = "thermostat1"; - private const string Thermostat2 = "thermostat2"; - - private static readonly Random s_random = new Random(); - private static readonly TimeSpan s_sleepDuration = TimeSpan.FromSeconds(5); - - private readonly DeviceClient _deviceClient; - private readonly ILogger _logger; - - // Dictionary to hold the temperature updates sent over each "Thermostat" component. - // NOTE: Memory constrained devices should leverage storage capabilities of an external service to store this - // information and perform computation. - // See https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services for more details. - private readonly Dictionary> _temperatureReadingsDateTimeOffset = - new Dictionary>(); - - // Dictionary to hold the current temperature for each "Thermostat" component. - private readonly Dictionary _temperature = new Dictionary(); - - // Dictionary to hold the max temperature since last reboot, for each "Thermostat" component. - private readonly Dictionary _maxTemp = new Dictionary(); - - public TemperatureControllerSample(DeviceClient deviceClient, ILogger logger) - { - _deviceClient = deviceClient ?? throw new ArgumentNullException($"{nameof(deviceClient)} cannot be null."); - _logger = logger ?? LoggerFactory.Create(builer => builer.AddConsole()).CreateLogger(); - } - - public async Task PerformOperationsAsync(CancellationToken cancellationToken) - { - // Set handler to receive and respond to writable property update requests. - _logger.LogDebug("Subscribe to writable property updates."); - await _deviceClient.SubscribeToWritablePropertiesEventAsync(HandlePropertyUpdatesAsync, null, cancellationToken); - - // Set handler to receive and respond to commands. - _logger.LogDebug($"Subscribe to commands."); - await _deviceClient.SubscribeToCommandsAsync(HandleCommandsAsync, null, cancellationToken); - - // Report device information on "deviceInformation" component. - // This is a component-level property update call. - await UpdateDeviceInformationPropertyAsync(cancellationToken); - - // Verify if the device has previously reported the current value for property "serialNumber". - // If the expected value has not been previously reported then send device serial number over property update. - // This is a top-level property update call. - await SendDeviceSerialNumberPropertyIfNotCurrentAsync(cancellationToken); - - bool temperatureReset = true; - _maxTemp[Thermostat1] = 0d; - _maxTemp[Thermostat2] = 0d; - - // Periodically send "temperature" over telemetry - on "Thermostat" components. - // Send "maxTempSinceLastReboot" over property update, when a new max temperature is reached - on "Thermostat" components. - while (!cancellationToken.IsCancellationRequested) - { - if (temperatureReset) - { - // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component. - _temperature[Thermostat1] = GenerateTemperatureWithinRange(45, 5); - _temperature[Thermostat2] = GenerateTemperatureWithinRange(45, 5); - } - - // Send temperature updates over telemetry and the value of max temperature since last reboot over property update. - // Both of these are component-level calls. - await SendTemperatureAsync(Thermostat1, cancellationToken); - await SendTemperatureAsync(Thermostat2, cancellationToken); - - // Send working set of device memory over telemetry. - // This is a top-level telemetry call. - await SendDeviceMemoryTelemetryAsync(cancellationToken); - - temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0; - await Task.Delay(s_sleepDuration); - } - } - - // The callback to handle property update requests. - private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableProperties, object userContext) - { - foreach (KeyValuePair writableProperty in writableProperties) - { - // The dispatcher key will be either a top-level property name or a component name. - switch (writableProperty.Key) - { - case Thermostat1: - case Thermostat2: - const string targetTemperatureProperty = "targetTemperature"; - if (writableProperties.TryGetValue(writableProperty.Key, targetTemperatureProperty, out double targetTemperatureRequested)) - { - await HandleTargetTemperatureUpdateRequestAsync(writableProperty.Key, targetTemperatureRequested, writableProperties.Version, userContext); - break; - } - else - { - _logger.LogWarning($"Property: Received an unrecognized property update from service for component {writableProperty.Key}:" + - $"\n[ {writableProperty.Value} ]."); - break; - } - - default: - _logger.LogWarning($"Property: Received an unrecognized property update from service:" + - $"\n[ {writableProperty.Key}: {writableProperty.Value} ]."); - break; - } - } - } - - // The callback to handle target temperature property update requests for a component. - private async Task HandleTargetTemperatureUpdateRequestAsync(string componentName, double targetTemperature, long version, object userContext) - { - const string targetTemperatureProperty = "targetTemperature"; - _logger.LogDebug($"Property: Received - component=\"{componentName}\", [ \"{targetTemperatureProperty}\": {targetTemperature}°C ]."); - - _temperature[componentName] = targetTemperature; - IWritablePropertyResponse writableResponse = _deviceClient - .PayloadConvention - .PayloadSerializer - .CreateWritablePropertyResponse(_temperature[componentName], CommonClientResponseCodes.OK, version, "Successfully updated target temperature."); - - var reportedProperty = new ClientPropertyCollection(); - reportedProperty.AddComponentProperty(componentName, targetTemperatureProperty, writableResponse); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); - - _logger.LogDebug($"Property: Update - component=\"{componentName}\", {reportedProperty.GetSerializedString()} is {nameof(CommonClientResponseCodes.OK)} " + - $"with a version of {updateResponse.Version}."); - } - - // The callback to handle command invocation requests. - private Task HandleCommandsAsync(CommandRequest commandRequest, object userContext) - { - // In this approach, we'll first switch through the component name returned and handle each component-level command. - // For the "default" case, we'll first check if the component name is null. - // If null, then this would be a top-level command request, so we'll switch through each top-level command. - // If not null, then this is a component-level command that has not been implemented. - - // Switch through CommandRequest.ComponentName to handle all component-level commands. - switch (commandRequest.ComponentName) - { - case Thermostat1: - case Thermostat2: - // For each component, switch through CommandRequest.CommandName to handle the specific component-level command. - switch (commandRequest.CommandName) - { - case "getMaxMinReport": - return HandleMaxMinReportCommandAsync(commandRequest, userContext); - - default: - _logger.LogWarning($"Received a command request that isn't" + - $" implemented - component name = {commandRequest.ComponentName}, command name = {commandRequest.CommandName}"); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - - // For the default case, first check if CommandRequest.ComponentName is null. - default: - // If CommandRequest.ComponentName is null, then this is a top-level command request. - if (commandRequest.ComponentName == null) - { - // Switch through CommandRequest.CommandName to handle all top-level commands. - switch (commandRequest.CommandName) - { - case "reboot": - return HandleRebootCommandAsync(commandRequest, userContext); - - default: - _logger.LogWarning($"Received a command request that isn't" + - $" implemented - command name = {commandRequest.CommandName}"); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - } - else - { - _logger.LogWarning($"Received a command request that isn't" + - $" implemented - component name = {commandRequest.ComponentName}, command name = {commandRequest.CommandName}"); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - } - } - - // The callback to handle top-level "reboot" command. - // This method will send a temperature update (of 0°C) over telemetry for both associated components. - private async Task HandleRebootCommandAsync(CommandRequest commandRequest, object userContext) - { - try - { - int delay = commandRequest.GetData(); - - _logger.LogDebug($"Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {delay} seconds)."); - await Task.Delay(delay * 1000); - - _temperature[Thermostat1] = _maxTemp[Thermostat1] = 0; - _temperature[Thermostat2] = _maxTemp[Thermostat2] = 0; - - _temperatureReadingsDateTimeOffset.Clear(); - _logger.LogDebug($"Command: Reboot completed."); - - return new CommandResponse(CommonClientResponseCodes.OK); - } - catch (JsonReaderException ex) - { - _logger.LogDebug($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); - return new CommandResponse(CommonClientResponseCodes.BadRequest); - } - } - - // The callback to handle component-level "getMaxMinReport" command. - // This method will returns the max, min and average temperature from the specified time to the current time. - private Task HandleMaxMinReportCommandAsync(CommandRequest commandRequest, object userContext) - { - try - { - DateTimeOffset sinceInUtc = commandRequest.GetData(); - _logger.LogDebug($"Command: Received - Generating max, min and avg temperature report since " + - $"{sinceInUtc.LocalDateTime}."); - - if (_temperatureReadingsDateTimeOffset.ContainsKey(commandRequest.ComponentName)) - { - Dictionary allReadings = _temperatureReadingsDateTimeOffset[commandRequest.ComponentName]; - Dictionary filteredReadings = allReadings.Where(i => i.Key > sinceInUtc) - .ToDictionary(i => i.Key, i => i.Value); - - if (filteredReadings != null && filteredReadings.Any()) - { - var report = new TemperatureReport - { - MaximumTemperature = filteredReadings.Values.Max(), - MinimumTemperature = filteredReadings.Values.Min(), - AverageTemperature = filteredReadings.Values.Average(), - StartTime = filteredReadings.Keys.Min(), - EndTime = filteredReadings.Keys.Max(), - }; - - _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\", MaxMinReport since {sinceInUtc.LocalDateTime}:" + - $" maxTemp={report.MaximumTemperature}, minTemp={report.MinimumTemperature}, avgTemp={report.AverageTemperature}, " + - $"startTime={report.StartTime.LocalDateTime}, endTime={report.EndTime.LocalDateTime}"); - - return Task.FromResult(new CommandResponse(report, CommonClientResponseCodes.OK)); - } - - _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\"," + - $" no relevant readings found since {sinceInUtc.LocalDateTime}, cannot generate any report."); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - - _logger.LogDebug($"Command: component=\"{commandRequest.ComponentName}\", no temperature readings sent yet," + - $" cannot generate any report."); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - catch (JsonReaderException ex) - { - _logger.LogError($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.BadRequest)); - } - } - - // Report the property values on "deviceInformation" component. - // This is a component-level property update call. - private async Task UpdateDeviceInformationPropertyAsync(CancellationToken cancellationToken) - { - const string componentName = "deviceInformation"; - var deviceInformationProperties = new Dictionary - { - { "manufacturer", "element15" }, - { "model", "ModelIDxcdvmk" }, - { "swVersion", "1.0.0" }, - { "osName", "Windows 10" }, - { "processorArchitecture", "64-bit" }, - { "processorManufacturer", "Intel" }, - { "totalStorage", 256 }, - { "totalMemory", 1024 }, - }; - var deviceInformation = new ClientPropertyCollection(); - deviceInformation.AddComponentProperties(componentName, deviceInformationProperties); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(deviceInformation, cancellationToken); - - _logger.LogDebug($"Property: Update - component = '{componentName}', properties update is complete " + - $"with a version of {updateResponse.Version}."); - } - - // Send working set of device memory over telemetry. - // This is a top-level telemetry call. - private async Task SendDeviceMemoryTelemetryAsync(CancellationToken cancellationToken) - { - const string workingSetName = "workingSet"; - long workingSet = Process.GetCurrentProcess().PrivateMemorySize64 / 1024; - using var telemetryMessage = new TelemetryMessage - { - Telemetry = { [workingSetName] = workingSet }, - }; - - await _deviceClient.SendTelemetryAsync(telemetryMessage, cancellationToken); - - _logger.LogDebug($"Telemetry: Sent - {telemetryMessage.Telemetry.GetSerializedString()} in KB."); - } - - // Verify if the device has previously reported the current value for property "serialNumber". - // If the expected value has not been previously reported then send device serial number over property update. - // This is a top-level property update call. - private async Task SendDeviceSerialNumberPropertyIfNotCurrentAsync(CancellationToken cancellationToken) - { - const string serialNumber = "serialNumber"; - const string currentSerialNumber = "SR-123456"; - - // Verify if the device has previously reported the current value for property "serialNumber". - // If the expected value has not been previously reported then report it. - - // Retrieve the device's properties. - ClientProperties properties = await _deviceClient.GetClientPropertiesAsync(cancellationToken); - - if (!properties.TryGetValue(serialNumber, out string serialNumberReported) - || serialNumberReported != currentSerialNumber) - { - var reportedProperties = new ClientPropertyCollection(); - reportedProperties.AddRootProperty(serialNumber, currentSerialNumber); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties, cancellationToken); - - _logger.LogDebug($"Property: Update - {reportedProperties.GetSerializedString()} is complete " + - $"with a version of {updateResponse.Version}."); - } - } - - // Send temperature updates over telemetry. - // This also sends the value of max temperature since last reboot over property update. - private async Task SendTemperatureAsync(string componentName, CancellationToken cancellationToken) - { - await SendTemperatureTelemetryAsync(componentName, cancellationToken); - - double maxTemp = _temperatureReadingsDateTimeOffset[componentName].Values.Max(); - if (maxTemp > _maxTemp[componentName]) - { - _maxTemp[componentName] = maxTemp; - await UpdateMaxTemperatureSinceLastRebootAsync(componentName, cancellationToken); - } - } - - // Send temperature update over telemetry. - // This is a component-level telemetry call. - private async Task SendTemperatureTelemetryAsync(string componentName, CancellationToken cancellationToken) - { - const string telemetryName = "temperature"; - double currentTemperature = _temperature[componentName]; - - using var telemtryMessage = new TelemetryMessage(componentName) - { - Telemetry = { [telemetryName] = currentTemperature }, - }; - - await _deviceClient.SendTelemetryAsync(telemtryMessage, cancellationToken); - - _logger.LogDebug($"Telemetry: Sent - component=\"{componentName}\", {telemtryMessage.Telemetry.GetSerializedString()} in °C."); - - if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName)) - { - _temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow, currentTemperature); - } - else - { - _temperatureReadingsDateTimeOffset.TryAdd( - componentName, - new Dictionary - { - { DateTimeOffset.UtcNow, currentTemperature }, - }); - } - } - - // Send temperature over reported property update. - // This is a component-level property update. - private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName, CancellationToken cancellationToken) - { - const string propertyName = "maxTempSinceLastReboot"; - double maxTemp = _maxTemp[componentName]; - var reportedProperties = new ClientPropertyCollection(); - reportedProperties.AddComponentProperty(componentName, propertyName, maxTemp); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties, cancellationToken); - - _logger.LogDebug($"Property: Update - component=\"{componentName}\", {reportedProperties.GetSerializedString()}" + - $" in °C is complete with a version of {updateResponse.Version}."); - } - - private static double GenerateTemperatureWithinRange(int max = 50, int min = 0) - { - return Math.Round(s_random.NextDouble() * (max - min) + min, 1); - } - } -} diff --git a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs b/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs deleted file mode 100644 index f7b25e9ec7..0000000000 --- a/iothub/device/samples/convention-based-samples/TemperatureController/TemperatureReport.cs +++ /dev/null @@ -1,26 +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.Text.Json.Serialization; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - public class TemperatureReport - { - [JsonPropertyName("maxTemp")] - public double MaximumTemperature { get; set; } - - [JsonPropertyName("minTemp")] - public double MinimumTemperature { get; set; } - - [JsonPropertyName("avgTemp")] - public double AverageTemperature { get; set; } - - [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } - - [JsonPropertyName("endTime")] - public DateTimeOffset EndTime { get; set; } - } -} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json b/iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json deleted file mode 100644 index 46b21f85cb..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/Models/Thermostat.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "@context": "dtmi:dtdl:context;2", - "@id": "dtmi:com:example:Thermostat;1", - "@type": "Interface", - "displayName": "Thermostat", - "description": "Reports current temperature and provides desired temperature control.", - "contents": [ - { - "@type": [ - "Telemetry", - "Temperature" - ], - "name": "temperature", - "displayName" : "Temperature", - "description" : "Temperature in degrees Celsius.", - "schema": "double", - "unit": "degreeCelsius" - }, - { - "@type": [ - "Property", - "Temperature" - ], - "name": "targetTemperature", - "schema": "double", - "displayName": "Target Temperature", - "description": "Allows to remotely specify the desired target temperature.", - "unit" : "degreeCelsius", - "writable": true - }, - { - "@type": [ - "Property", - "Temperature" - ], - "name": "maxTempSinceLastReboot", - "schema": "double", - "unit" : "degreeCelsius", - "displayName": "Max temperature since last reboot.", - "description": "Returns the max temperature since last device reboot." - }, - { - "@type": "Command", - "name": "getMaxMinReport", - "displayName": "Get Max-Min report.", - "description": "This command returns the max, min and average temperature from the specified time to the current time.", - "request": { - "name": "since", - "displayName": "Since", - "description": "Period to return the max-min report.", - "schema": "dateTime" - }, - "response": { - "name" : "tempReport", - "displayName": "Temperature Report", - "schema": { - "@type": "Object", - "fields": [ - { - "name": "maxTemp", - "displayName": "Max temperature", - "schema": "double" - }, - { - "name": "minTemp", - "displayName": "Min temperature", - "schema": "double" - }, - { - "name" : "avgTemp", - "displayName": "Average Temperature", - "schema": "double" - }, - { - "name" : "startTime", - "displayName": "Start Time", - "schema": "dateTime" - }, - { - "name" : "endTime", - "displayName": "End Time", - "schema": "dateTime" - } - ] - } - } - } - ] -} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs b/iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs deleted file mode 100644 index 9f8f5b99ff..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/Parameter.cs +++ /dev/null @@ -1,86 +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 CommandLine; -using Microsoft.Extensions.Logging; -using System; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - /// - /// Parameters for the application supplied via command line arguments. - /// If the parameter is not supplied via command line args, it will look for it in environment variables. - /// - internal class Parameters - { - [Option( - "DeviceSecurityType", - HelpText = "(Required) The flow that will be used for connecting the device for the sample. Possible case-insensitive values include: dps, connectionString." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\".")] - public string DeviceSecurityType { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_SECURITY_TYPE"); - - [Option( - 'p', - "PrimaryConnectionString", - HelpText = "(Required if DeviceSecurityType is \"connectionString\"). \nThe primary connection string for the device to simulate." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_CONNECTION_STRING\".")] - public string PrimaryConnectionString { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_CONNECTION_STRING"); - - [Option( - 'e', - "DpsEndpoint", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS endpoint to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ENDPOINT\".")] - public string DpsEndpoint { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ENDPOINT"); - - [Option( - 'i', - "DpsIdScope", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe DPS ID Scope to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_ID_SCOPE\".")] - public string DpsIdScope { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_ID_SCOPE"); - - [Option( - 'd', - "DeviceId", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device registration Id to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_ID\".")] - public string DeviceId { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_ID"); - - [Option( - 'k', - "DeviceSymmetricKey", - HelpText = "(Required if DeviceSecurityType is \"dps\"). \nThe device symmetric key to use during device provisioning." + - "\nDefaults to environment variable \"IOTHUB_DEVICE_DPS_DEVICE_KEY\".")] - public string DeviceSymmetricKey { get; set; } = Environment.GetEnvironmentVariable("IOTHUB_DEVICE_DPS_DEVICE_KEY"); - - [Option( - 'r', - "Application running time (in seconds)", - Required = false, - HelpText = "The running time for this console application. Leave it unassigned to run the application until it is explicitly canceled using Control+C.")] - public double? ApplicationRunningTime { get; set; } - - public bool Validate(ILogger logger) - { - if (string.IsNullOrWhiteSpace(DeviceSecurityType)) - { - logger.LogWarning("Device provisioning type not set, please set the environment variable \"IOTHUB_DEVICE_SECURITY_TYPE\"" + - "or pass in \"-s | --DeviceSecurityType\" through command line. \nWill default to using \"dps\" flow."); - - DeviceSecurityType = "dps"; - } - - return (DeviceSecurityType.ToLowerInvariant()) switch - { - "dps" => !string.IsNullOrWhiteSpace(DpsEndpoint) - && !string.IsNullOrWhiteSpace(DpsIdScope) - && !string.IsNullOrWhiteSpace(DeviceId) - && !string.IsNullOrWhiteSpace(DeviceSymmetricKey), - "connectionstring" => !string.IsNullOrWhiteSpace(PrimaryConnectionString), - _ => throw new ArgumentException($"Unrecognized value for device provisioning received: {DeviceSecurityType}." + - $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."), - }; - } - } -} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Program.cs b/iothub/device/samples/convention-based-samples/Thermostat/Program.cs deleted file mode 100644 index 69dff54071..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/Program.cs +++ /dev/null @@ -1,162 +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 CommandLine; -using Microsoft.Azure.Devices.Provisioning.Client; -using Microsoft.Azure.Devices.Provisioning.Client.Transport; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Extensions.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - public class Program - { - // DTDL interface used: https://github.com/Azure/iot-plugandplay-models/blob/main/dtmi/com/example/thermostat-1.json - private const string ModelId = "dtmi:com:example:Thermostat;1"; - - private static ILogger s_logger; - - public static async Task Main(string[] args) - { - // Parse application parameters - Parameters parameters = null; - ParserResult result = Parser.Default.ParseArguments(args) - .WithParsed(parsedParams => - { - parameters = parsedParams; - }) - .WithNotParsed(errors => - { - Environment.Exit(1); - }); - - s_logger = InitializeConsoleDebugLogger(); - if (!parameters.Validate(s_logger)) - { - throw new ArgumentException("Required parameters are not set. Please recheck required variables by using \"--help\""); - } - - var runningTime = parameters.ApplicationRunningTime != null - ? TimeSpan.FromSeconds((double)parameters.ApplicationRunningTime) - : Timeout.InfiniteTimeSpan; - - s_logger.LogInformation("Press Control+C to quit the sample."); - using var cts = new CancellationTokenSource(runningTime); - Console.CancelKeyPress += (sender, eventArgs) => - { - eventArgs.Cancel = true; - cts.Cancel(); - s_logger.LogInformation("Sample execution cancellation requested; will exit."); - }; - - s_logger.LogDebug($"Set up the device client."); - using DeviceClient deviceClient = await SetupDeviceClientAsync(parameters, cts.Token); - var sample = new ThermostatSample(deviceClient, s_logger); - await sample.PerformOperationsAsync(cts.Token); - - // PerformOperationsAsync is designed to run until cancellation has been explicitly requested, either through - // cancellation token expiration or by Console.CancelKeyPress. - // As a result, by the time the control reaches the call to close the device client, the cancellation token source would - // have already had cancellation requested. - // Hence, if you want to pass a cancellation token to any subsequent calls, a new token needs to be generated. - // For device client APIs, you can also call them without a cancellation token, which will set a default - // cancellation timeout of 4 minutes: https://github.com/Azure/azure-iot-sdk-csharp/blob/64f6e9f24371bc40ab3ec7a8b8accbfb537f0fe1/iothub/device/src/InternalClient.cs#L1922 - await deviceClient.CloseAsync(); - } - - private static ILogger InitializeConsoleDebugLogger() - { - ILoggerFactory loggerFactory = LoggerFactory.Create(builder => - { - builder - .AddFilter(level => level >= LogLevel.Debug) - .AddConsole(options => - { - options.TimestampFormat = "[MM/dd/yyyy HH:mm:ss]"; - }); - }); - - return loggerFactory.CreateLogger(); - } - - private static async Task SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken) - { - DeviceClient deviceClient; - switch (parameters.DeviceSecurityType.ToLowerInvariant()) - { - case "dps": - s_logger.LogDebug($"Initializing via DPS"); - DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken); - var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey); - deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod); - break; - - case "connectionstring": - s_logger.LogDebug($"Initializing via IoT Hub connection string"); - deviceClient = InitializeDeviceClient(parameters.PrimaryConnectionString); - break; - - default: - throw new ArgumentException($"Unrecognized value for device provisioning received: {parameters.DeviceSecurityType}." + - $" It should be either \"dps\" or \"connectionString\" (case-insensitive)."); - } - - return deviceClient; - } - - // Provision a device via DPS, by sending the PnP model Id as DPS payload. - private static async Task ProvisionDeviceAsync(Parameters parameters, CancellationToken cancellationToken) - { - SecurityProvider symmetricKeyProvider = new SecurityProviderSymmetricKey(parameters.DeviceId, parameters.DeviceSymmetricKey, null); - ProvisioningTransportHandler mqttTransportHandler = new ProvisioningTransportHandlerMqtt(); - ProvisioningDeviceClient pdc = ProvisioningDeviceClient.Create(parameters.DpsEndpoint, parameters.DpsIdScope, - symmetricKeyProvider, mqttTransportHandler); - - var pnpPayload = new ProvisioningRegistrationAdditionalData - { - JsonData = $"{{ \"modelId\": \"{ModelId}\" }}", - }; - return await pdc.RegisterAsync(pnpPayload, cancellationToken); - } - - // Initialize the device client instance using connection string based authentication, over Mqtt protocol (TCP, with fallback over Websocket) - // and setting the ModelId into ClientOptions. - // This method also sets a connection status change callback, that will get triggered any time the device's connection status changes. - private static DeviceClient InitializeDeviceClient(string deviceConnectionString) - { - var options = new ClientOptions - { - ModelId = ModelId, - }; - - DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); - deviceClient.SetConnectionStatusChangesHandler((status, reason) => - { - s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); - }); - - return deviceClient; - } - - // Initialize the device client instance using symmetric key based authentication, over Mqtt protocol (TCP, with fallback over Websocket) and setting the ModelId into ClientOptions. - // This method also sets a connection status change callback, that will get triggered any time the device's connection status changes. - private static DeviceClient InitializeDeviceClient(string hostname, IAuthenticationMethod authenticationMethod) - { - var options = new ClientOptions - { - ModelId = ModelId, - }; - - DeviceClient deviceClient = DeviceClient.Create(hostname, authenticationMethod, TransportType.Mqtt, options); - deviceClient.SetConnectionStatusChangesHandler((status, reason) => - { - s_logger.LogDebug($"Connection status change registered - status={status}, reason={reason}."); - }); - - return deviceClient; - } - } -} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json b/iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json deleted file mode 100644 index 6ae8d6e736..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/Properties/launchSettings.template.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "profiles": { - "Hub": { - "commandName": "Project", - "environmentVariables": { - "IOTHUB_DEVICE_SECURITY_TYPE": "connectionString", - "IOTHUB_DEVICE_CONNECTION_STRING": "" - } - }, - "DPS": { - "commandName": "Project", - "environmentVariables": { - "IOTHUB_DEVICE_SECURITY_TYPE": "dps", - "IOTHUB_DEVICE_DPS_ID_SCOPE": "", - "IOTHUB_DEVICE_DPS_DEVICE_ID": "", - "IOTHUB_DEVICE_DPS_DEVICE_KEY": "", - "IOTHUB_DEVICE_DPS_ENDPOINT": "global.azure-devices-provisioning.net" - }, - "sqlDebugging": false - } - } -} \ No newline at end of file diff --git a/iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs b/iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs deleted file mode 100644 index 44996a7961..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/TemperatureReport.cs +++ /dev/null @@ -1,26 +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 Newtonsoft.Json; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - public class TemperatureReport - { - [JsonProperty("maxTemp")] - public double MaximumTemperature { get; set; } - - [JsonProperty("minTemp")] - public double MinimumTemperature { get; set; } - - [JsonProperty("avgTemp")] - public double AverageTemperature { get; set; } - - [JsonProperty("startTime")] - public DateTimeOffset StartTime { get; set; } - - [JsonProperty("endTime")] - public DateTimeOffset EndTime { get; set; } - } -} diff --git a/iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj b/iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj deleted file mode 100644 index d0fbcbc34a..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/Thermostat.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - netcoreapp3.1 - $(MSBuildProjectDirectory)\..\..\..\..\.. - - - - - - - - - - - - - diff --git a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs b/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs deleted file mode 100644 index d4977479d0..0000000000 --- a/iothub/device/samples/convention-based-samples/Thermostat/ThermostatSample.cs +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// 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.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; - -namespace Microsoft.Azure.Devices.Client.Samples -{ - public class ThermostatSample - { - private static readonly Random s_random = new Random(); - private static readonly TimeSpan s_sleepDuration = TimeSpan.FromSeconds(5); - - private double _temperature = 0d; - private double _maxTemp = 0d; - - // Dictionary to hold the temperature updates sent over. - // NOTE: Memory constrained devices should leverage storage capabilities of an external service to store this information and perform computation. - // See https://docs.microsoft.com/en-us/azure/event-grid/compare-messaging-services for more details. - private readonly Dictionary _temperatureReadingsDateTimeOffset = new Dictionary(); - - private readonly DeviceClient _deviceClient; - private readonly ILogger _logger; - - public ThermostatSample(DeviceClient deviceClient, ILogger logger) - { - _deviceClient = deviceClient ?? throw new ArgumentNullException($"{nameof(deviceClient)} cannot be null."); - _logger = logger ?? LoggerFactory.Create(builer => builer.AddConsole()).CreateLogger(); - } - - public async Task PerformOperationsAsync(CancellationToken cancellationToken) - { - // Set handler to receive and respond to writable property update requests. - _logger.LogDebug($"Subscribe to writable property updates."); - await _deviceClient.SubscribeToWritablePropertiesEventAsync(HandlePropertyUpdatesAsync, null, cancellationToken); - - // Set handler to receive and respond to commands. - _logger.LogDebug($"Subscribe to commands."); - await _deviceClient.SubscribeToCommandsAsync(HandleCommandsAsync, null, cancellationToken); - - bool temperatureReset = true; - - // Periodically send "temperature" over telemetry. - // Send "maxTempSinceLastReboot" over property update, when a new max temperature is reached. - while (!cancellationToken.IsCancellationRequested) - { - if (temperatureReset) - { - // Generate a random value between 5.0°C and 45.0°C for the current temperature reading. - _temperature = GenerateTemperatureWithinRange(45, 5); - temperatureReset = false; - } - - // Send temperature updates over telemetry and the value of max temperature since last reboot over property update. - await SendTemperatureAsync(); - - await Task.Delay(s_sleepDuration); - } - } - - // The callback to handle property update requests. - private async Task HandlePropertyUpdatesAsync(ClientPropertyCollection writableProperties, object userContext) - { - foreach (KeyValuePair writableProperty in writableProperties) - { - switch (writableProperty.Key) - { - case "targetTemperature": - const string targetTemperatureProperty = "targetTemperature"; - double targetTemperatureRequested = Convert.ToDouble(writableProperty.Value); - _logger.LogDebug($"Property: Received - [ \"{targetTemperatureProperty}\": {targetTemperatureRequested}°C ]."); - - _temperature = targetTemperatureRequested; - IWritablePropertyResponse writableResponse = _deviceClient - .PayloadConvention - .PayloadSerializer - .CreateWritablePropertyResponse(_temperature, CommonClientResponseCodes.OK, writableProperties.Version, "Successfully updated target temperature"); - - var reportedProperty = new ClientPropertyCollection(); - reportedProperty.AddRootProperty(targetTemperatureProperty, writableResponse); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperty); - - _logger.LogDebug($"Property: Update - {reportedProperty.GetSerializedString()} is {nameof(CommonClientResponseCodes.OK)} " + - $"with a version of {updateResponse.Version}."); - - break; - - default: - _logger.LogWarning($"Property: Received an unrecognized property update from service:\n[ {writableProperty.Key}: {writableProperty.Value} ]."); - break; - } - } - } - - // The callback to handle command invocation requests. - private Task HandleCommandsAsync(CommandRequest commandRequest, object userContext) - { - // In this approach, we'll switch through the command name returned and handle each top-level command. - switch (commandRequest.CommandName) - { - case "getMaxMinReport": - try - { - DateTimeOffset sinceInUtc = commandRequest.GetData(); - _logger.LogDebug($"Command: Received - Generating max, min and avg temperature report since " + - $"{sinceInUtc.LocalDateTime}."); - - Dictionary filteredReadings = _temperatureReadingsDateTimeOffset - .Where(i => i.Key > sinceInUtc) - .ToDictionary(i => i.Key, i => i.Value); - - if (filteredReadings != null && filteredReadings.Any()) - { - var report = new TemperatureReport - { - MaximumTemperature = filteredReadings.Values.Max(), - MinimumTemperature = filteredReadings.Values.Min(), - AverageTemperature = filteredReadings.Values.Average(), - StartTime = filteredReadings.Keys.Min(), - EndTime = filteredReadings.Keys.Max(), - }; - - _logger.LogDebug($"Command: MaxMinReport since {sinceInUtc.LocalDateTime}:" + - $" maxTemp={report.MaximumTemperature}, minTemp={report.MinimumTemperature}, avgTemp={report.AverageTemperature}, " + - $"startTime={report.StartTime.LocalDateTime}, endTime={report.EndTime.LocalDateTime}"); - - return Task.FromResult(new CommandResponse(report, CommonClientResponseCodes.OK)); - } - - _logger.LogDebug($"Command: No relevant readings found since {sinceInUtc.LocalDateTime}, cannot generate any report."); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - catch (JsonReaderException ex) - { - _logger.LogError($"Command input for {commandRequest.CommandName} is invalid: {ex.Message}."); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.BadRequest)); - } - - default: - _logger.LogWarning($"Received a command request that isn't" + - $" implemented - command name = {commandRequest.CommandName}"); - - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - } - - // Send temperature updates over telemetry. - // This also sends the value of max temperature since last reboot over property update. - private async Task SendTemperatureAsync() - { - await SendTemperatureTelemetryAsync(); - - double maxTemp = _temperatureReadingsDateTimeOffset.Values.Max(); - if (maxTemp > _maxTemp) - { - _maxTemp = maxTemp; - await UpdateMaxTemperatureSinceLastRebootPropertyAsync(); - } - } - - // Send temperature update over telemetry. - private async Task SendTemperatureTelemetryAsync() - { - const string telemetryName = "temperature"; - - using var telemetryMessage = new TelemetryMessage - { - Telemetry = { [telemetryName] = _temperature } - }; - await _deviceClient.SendTelemetryAsync(telemetryMessage); - - _logger.LogDebug($"Telemetry: Sent - {telemetryMessage.Telemetry.GetSerializedString()}."); - _temperatureReadingsDateTimeOffset.Add(DateTimeOffset.Now, _temperature); - } - - // Send temperature over reported property update. - private async Task UpdateMaxTemperatureSinceLastRebootPropertyAsync() - { - const string propertyName = "maxTempSinceLastReboot"; - var reportedProperties = new ClientPropertyCollection(); - reportedProperties.AddRootProperty(propertyName, _maxTemp); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(reportedProperties); - - _logger.LogDebug($"Property: Update - {reportedProperties.GetSerializedString()} is {nameof(CommonClientResponseCodes.OK)} " + - $"with a version of {updateResponse.Version}."); - } - - private static double GenerateTemperatureWithinRange(int max = 50, int min = 0) - { - return Math.Round(s_random.NextDouble() * (max - min) + min, 1); - } - } -} diff --git a/iothub/device/samples/convention-based-samples/readme.md b/iothub/device/samples/convention-based-samples/readme.md deleted file mode 100644 index de7263a860..0000000000 --- a/iothub/device/samples/convention-based-samples/readme.md +++ /dev/null @@ -1,577 +0,0 @@ ---- -page_type: sample -description: "A set of samples that show how a device that uses the IoT Plug and Play conventions interacts with either IoT Hub or IoT Central." -languages: -- csharp -products: -- azure -- azure-iot-hub -- azure-iot-central -- azure-iot-pnp -- dotnet -urlFragment: azure-iot-pnp-device-samples-for-csharp-net ---- - -# IoT Plug And Play (PnP) device/module APIs - -Device(s)/module(s) connecting to IoT Hub that announce their DTDL model ID during initialization can now perform convention-based operations. One such convention supported is [IoT Plug and Play][pnp-convention]. - -These devices/modules can now use the native PnP APIs in the Azure IoT device SDKs to directly exchange messages with an IoT Hub, without having to manually format these messages to follow the PnP convention. - -## Table of Contents - -- [Client initialization](#client-initialization) - - [Announce model ID during client initialization (same as in latest `master` release)](#announce-model-ID-during-client-initialization-same-as-in-latest-master-release) - - [Define the serialization and encoding convention that the client follows (newly introduced in `preview`)](#define-the-serialization-and-encoding-convention-that-the-client-follows-newly-introduced-in-preview) -- [Terms used](#terms-used) -- [Comparison of API calls - non-convention-aware APIs (old) vs convention-aware APIs (new):](#comparison-of-api-calls---non-convention-aware-apis-old-vs-convention-aware-apis-new) - - [Telemetry](#telemetry) - - [Commands](#commands) - - [Properties](#properties) -- [IoT Plug And Play device samples](#iot-plug-and-play-device-samples) - -## Client initialization - -### Announce model ID during client initialization (same as in latest [`master`][latest-master-release] release) - -```csharp -var options = new ClientOptions -{ - ModelId = ModelId, -}; - -DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); -``` - -### Define the serialization and encoding convention that the client follows (newly introduced in [`preview`][latest-preview-release]) - -```csharp -// Specify a custom System.Text.Json serialization and Utf8 encoding based PayloadConvention to be used. -// If not specified, the library defaults to a convention that uses Newtonsoft.Json-based serializer and Utf8-based encoder. -var options = new ClientOptions(SystemTextJsonPayloadConvention.Instance) -{ - ModelId = ModelId, -}; - -DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options); -``` - -## Terms used: -Telemetry, commands, properties and components can all be defined in the contents section of the main interface of a DTDL v2 model. Components enable interfaces to be composed of other interfaces. - -In DTDL v2, a component cannot contain another component. The maximum depth of components is 1. - -- Top-level telemetry/commands/properties - - These refer to the telemetry, commands and properties that are defined directly in the contents section of the main interface of a DTDL v2 model. In case of a model with no components, the main interface refers to the default component. - - When working with this category of telemetry, commands and properties, you do not need to specify any component name. -- Component-level telemetry/commands/properties - - These refer to the telemetry, commands and properties that are defined in the contents section of an interface, which itself is defined as a component within the main interface. - - When working with this category of telemetry, commands and properties, you need to specify the name of the component that these contents belong to. - -## Comparison of API calls - non-convention-aware APIs (old) vs convention-aware APIs (new): - -The following section provides a comparison between the older non-convention-aware APIs (as per latest [`master`][latest-master-release] release) and the newly introduced convention-aware APIs (as per latest [`preview`][latest-preview-release] release). - -## Telemetry - -### Send top-level telemetry: - -#### Using non-convention-aware API (old): - -```csharp -// Send telemetry "serialNumber". -string serialNumber = "SR-1234"; -var telemetry = new Dictionary -{ - ["serialNumber"] = serialNumber, -}; - -using var message = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(telemetry))) -{ - MessageId = s_random.Next().ToString(), - ContentEncoding = "utf-8", - ContentType = "application/json", -}; -await _deviceClient.SendEventAsync(message, cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Send telemetry "serialNumber". -string serialNumber = "SR-1234"; -using var telemetryMessage = new TelemetryMessage -{ - MessageId = Guid.NewGuid().ToString(), - Telemetry = { ["serialNumber"] = serialNumber }, -}; - -await _deviceClient.SendTelemetryAsync(telemetryMessage, cancellationToken); -``` - -### Send component-level telemetry: - -#### Using non-convention-aware API (old): - -```csharp -// Send telemetry "serialNumber" under component "thermostat1". -string serialNumber = "SR-1234"; -var telemetry = new Dictionary() -{ - ["serialNumber"] = serialNumber, -}; - -using var message = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(telemetry))) -{ - MessageId = s_random.Next().ToString(), - ContentEncoding = "utf-8", - ContentType = "application/json", - ComponentName = "thermostat1", -}; -await _deviceClient.SendEventAsync(message, cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Send telemetry "serialNumber" under component "thermostat1". -string serialNumber = "SR-1234"; -using var telemtryMessage = new TelemetryMessage("thermostat1") -{ - MessageId = Guid.NewGuid().ToString(), - Telemetry = { ["serialNumber"] = serialNumber }, -}; - -await _deviceClient.SendTelemetryAsync(telemtryMessage, cancellationToken); -``` - -## Commands - -### Respond to top-level commands: - -#### Using non-convention-aware API (old): - -```csharp -// Subscribe and respond to command "reboot". -await _deviceClient.SetMethodHandlerAsync( - "reboot", - async (methodRequest, userContext) => - { - try - { - int delay = JsonConvert.DeserializeObject(methodRequest.DataAsJson); - await Task.Delay(TimeSpan.FromSeconds(delay)); - - // Application code ... - - return new MethodResponse(CommonClientResponseCodes.OK); - } - catch (JsonReaderException) - { - return new MethodResponse(CommonClientResponseCodes.BadRequest); - } - }, - null, - cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Subscribe and respond to command "reboot". -await _deviceClient.SubscribeToCommandsAsync( - async (commandRequest, userContext) => - { - // This API does not support setting command-level callbacks. - // For this reason we'll need to inspect the commandRequest.CommandName for the request command and perform the actions accordingly. - // Refer to the ThermostatSample.cs for a complete sample implementation. - - if (commandRequest.CommandName == "reboot") - { - try - { - int delay = commandRequest.GetData(); - await Task.Delay(delay * 1000); - - // Application code ... - - return new CommandResponse(CommonClientResponseCodes.OK); - } - catch (JsonReaderException) - { - return new CommandResponse(CommonClientResponseCodes.BadRequest); - } - } - else - { - return new CommandResponse(CommonClientResponseCodes.NotFound); - } - }, - null, - cancellationToken); -``` - -### Respond to component-level commands: - -#### Using non-convention-aware API (old): - -```csharp -// Subscribe and respond to command "getMaxMinReport" under component "thermostat1". -// The method that the application subscribes to is in the format {componentName}*{commandName}. -await _deviceClient.SetMethodHandlerAsync( - "thermostat1*getMaxMinReport", - (commandRequest, userContext) => - { - try - { - DateTimeOffset sinceInUtc = JsonConvert.DeserializeObject(commandRequest.DataAsJson); - - // Application code ... - Report report = GetMaxMinReport(sinceInUtc); - - return Task.FromResult( - new MethodResponse( - Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report)), - CommonClientResponseCodes.OK)); - } - catch (JsonReaderException) - { - return Task.FromResult(new MethodResponse(CommonClientResponseCodes.BadRequest)); - } - }, - null, - cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Subscribe and respond to command "getMaxMinReport" under component "thermostat1". -await _deviceClient.SubscribeToCommandsAsync( - (commandRequest, userContext) => - { - // This API does not support setting command-level callbacks. - // For this reason we'll need to inspect both commandRequest.ComponentName and commandRequest.CommandName, and perform the actions accordingly. - // Refer to the TemperatureControllerSample.cs for a complete sample implementation. - - if (commandRequest.ComponentName == "thermostat1" - && commandRequest.CommandName == "getMaxMinReport") - { - try - { - DateTimeOffset sinceInUtc = commandRequest.GetData(); - - // Application code ... - Report report = GetMaxMinReport(sinceInUtc); - - return Task.FromResult(new CommandResponse(report, CommonClientResponseCodes.OK)); - } - catch (JsonReaderException) - { - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.BadRequest)); - } - } - else - { - return Task.FromResult(new CommandResponse(CommonClientResponseCodes.NotFound)); - } - } -); -``` - -## Properties - -### Retrive top-level client properties: - -#### Using non-convention-aware API (old): - -```csharp -// Retrieve the client's properties. -Twin properties = await _deviceClient.GetTwinAsync(cancellationToken); - -// To fetch the value of client reported property "serialNumber". -bool isSerialNumberReported = properties.Properties.Reported.Contains("serialNumber"); -if (isSerialNumberReported) -{ - string serialNumberReported = properties.Properties.Reported["serialNumber"]; -} - -// To fetch the value of service requested "targetTemperature" value. -bool isTargetTemperatureUpdateRequested = properties.Properties.Desired.Contains("targetTemperature"); -if (isTargetTemperatureUpdateRequested) -{ - double targetTemperatureUpdateRequest = properties.Properties.Desired["targetTemperature"]; -} -``` - -#### Using convention-aware API (new): - -```csharp -// Retrieve the client's properties. - ClientProperties properties = await _deviceClient.GetClientPropertiesAsync(cancellationToken); - -// To fetch the value of client reported property "serialNumber". -bool isSerialNumberReported = properties.TryGetValue("serialNumber", out string serialNumberReported); - - -// To fetch the value of service requested "targetTemperature" value. -bool isTargetTemperatureUpdateRequested = properties.Writable.TryGetValue("targetTemperature", out double targetTemperatureUpdateRequest); -``` - -### Retrive component-level client properties: - -#### Using non-convention-aware API (old): - -```csharp -// Retrieve the client's properties. -Twin properties = await _deviceClient.GetTwinAsync(cancellationToken); - -// To fetch the value of client reported property "serialNumber" under component "thermostat1". -JToken serialNumberJToken = null; -bool isSerialNumberReported = properties.Properties.Reported.Contains("thermostat1") - && ((JObject)properties.Properties.Reported["thermostat1"]).TryGetValue("serialNumber", out serialNumberJToken); - -if (isSerialNumberReported) -{ - string serialNumberReported = serialNumberJToken?.ToObject(); -} - -// To fetch the value of service requested "targetTemperature" value under component "thermostat1". -JToken targetTemperatureUpdateRequestJToken = null; -bool isTargetTemperatureUpdateRequested = properties.Properties.Desired.Contains("thermostat1") - && ((JObject)properties.Properties.Desired["thermostat1"]).TryGetValue("targetTemperature", out targetTemperatureUpdateRequestJToken); - -if (isTargetTemperatureUpdateRequested) -{ - double targetTemperatureUpdateRequest = (double)(targetTemperatureUpdateRequestJToken?.ToObject()); -} -``` - -#### Using convention-aware API (new): - -```csharp -// Retrieve the client's properties. - ClientProperties properties = await _deviceClient.GetClientPropertiesAsync(cancellationToken); - -// To fetch the value of client reported property "serialNumber" under component "thermostat1". -bool isSerialNumberReported = properties.TryGetValue("thermostat1", "serialNumber", out string serialNumberReported); - - -// To fetch the value of service requested "targetTemperature" value under component "thermostat1". -bool isTargetTemperatureUpdateRequested = properties.Writable.TryGetValue("thermostat1", "targetTemperature", out double targetTemperatureUpdateRequest); -``` - -### Update top-level property: - -#### Using non-convention-aware API (old): - -```csharp -// Update the property "serialNumber". -var propertiesToBeUpdated = new TwinCollection -{ - ["serialNumber"] = "SR-1234", -}; -await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Update the property "serialNumber". -var propertiesToBeUpdated = new ClientPropertyCollection -{ - ["serialNumber"] = "SR-1234", -}; -ClientPropertiesUpdateResponse updateResponse = await _deviceClient - .UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); -long updatedVersion = updateResponse.Version; -``` - -### Update component-level properties: - -#### Using non-convention-aware API (old): - -```csharp -// Update the property "serialNumber" under component "thermostat1". -// When calling the UpdateReportedPropertiesAsync API the component-level property update requests must -// include the {"__t": "c"} marker to indicate that the element refers to a component. -var thermostatProperties = new TwinCollection -{ - ["__t"] = "c", - ["serialNumber"] = "SR-1234", -}; -var propertiesToBeUpdated = new TwinCollection -{ - ["thermostat1"] = thermostatProperties -}; -await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Update the property "serialNumber" under component "thermostat1". -var propertiesToBeUpdated = new ClientPropertyCollection(); -propertiesToBeUpdated.AddComponentProperty("thermostat1", "serialNumber", "SR-1234"); - -ClientPropertiesUpdateResponse updateResponse = await _deviceClient - .UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); -long updatedVersion = updateResponse.Version; -``` - -### Respond to top-level property update requests: - -#### Using non-convention-aware API (old): - -```csharp -// Subscribe and respond to event for writable property "targetTemperature". -// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. -await _deviceClient.SetDesiredPropertyUpdateCallbackAsync( - async (desired, userContext) => - { - if (desired.Contains("targetTemperature")) - { - double targetTemperature = desired["targetTemperature"]; - - var targetTemperatureUpdateResponse = new TwinCollection - { - ["value"] = targetTemperature, - ["ac"] = CommonClientResponseCodes.OK, - ["av"] = desired.Version, - ["ad"] = "The operation completed successfully." - }; - var propertiesToBeUpdated = new TwinCollection() - { - ["targetTemperature"] = targetTemperatureUpdateResponse, - }; - - await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); - } - }, - null, - cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Subscribe and respond to event for writable property "targetTemperature". -// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. -await _deviceClient.SubscribeToWritablePropertiesEventAsync( - async (writableProperties, userContext) => - { - if (writableProperties.TryGetValue("targetTemperature", out double targetTemperature)) - { - IWritablePropertyResponse writableResponse = _deviceClient - .PayloadConvention - .PayloadSerializer - .CreateWritablePropertyResponse(targetTemperature, CommonClientResponseCodes.OK, writableProperties.Version, "The operation completed successfully."); - - var propertiesToBeUpdated = new ClientPropertyCollection(); - propertiesToBeUpdated.AddRootProperty("targetTemperature", writableResponse); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); - } - }, - null, - cancellationToken); -``` - -### Respond to component-level property update requests: - -#### Using non-convention-aware API (old): - -```csharp -// Subscribe and respond to event for writable property "targetTemperature" -// under component "thermostat1". -// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. -// When calling the UpdateReportedPropertiesAsync API the component-level property update requests must -// include the {"__t": "c"} marker to indicate that the element refers to a component. -await _deviceClient.SetDesiredPropertyUpdateCallbackAsync( - async (desired, userContext) => - { - if (desired.Contains("thermostat1") - && ((JObject)desired["thermostat1"]) - .TryGetValue("targetTemperature", out JToken targetTemperatureRequested)) - { - double targetTemperature = targetTemperatureRequested - .ToObject(); - - var targetTemperatureUpdateResponse = new TwinCollection - { - ["value"] = targetTemperature, - ["ac"] = CommonClientResponseCodes.OK, - ["av"] = desired.Version, - ["ad"] = "The operation completed successfully." - }; - var thermostatProperties = new TwinCollection() - { - ["__t"] = "c", - ["targetTemperature"] = targetTemperatureUpdateResponse, - }; - var propertiesToBeUpdated = new TwinCollection() - { - ["thermostat1"] = thermostatProperties, - }; - - await _deviceClient.UpdateReportedPropertiesAsync(propertiesToBeUpdated, cancellationToken); - } - }, - null, - cancellationToken); -``` - -#### Using convention-aware API (new): - -```csharp -// Subscribe and respond to event for writable property "targetTemperature" -// under component "thermostat1". -// This writable property update response should follow the format specified here: https://docs.microsoft.com/azure/iot-pnp/concepts-convention#writable-properties. -await _deviceClient.SubscribeToWritablePropertiesEventAsync( - async (writableProperties, userContext) => - { - if (writableProperties.TryGetValue("thermostat1", "targetTemperature", out double targetTemperature)) - { - IWritablePropertyResponse writableResponse = _deviceClient - .PayloadConvention - .PayloadSerializer - .CreateWritablePropertyResponse(targetTemperature, CommonClientResponseCodes.OK, writableProperties.Version, "The operation completed successfully."); - - var propertiesToBeUpdated = new ClientPropertyCollection(); - propertiesToBeUpdated.AddComponentProperty("thermostat1", "targetTemperature", writableResponse); - - ClientPropertiesUpdateResponse updateResponse = await _deviceClient.UpdateClientPropertiesAsync(propertiesToBeUpdated, cancellationToken); - } - }, - null, - cancellationToken); -``` - -# IoT Plug And Play device samples - -These samples demonstrate how a device that follows the [IoT Plug and Play conventions][pnp-convention] interacts with IoT Hub or IoT Central, to: - -- Send telemetry. -- Update client properties and be notified of service requested property update requests. -- Respond to command invocation. - -The samples demonstrate two scenarios: - -- An IoT Plug and Play device that implements the [Thermostat][d-thermostat] model. This model has a single interface (the default component) that defines telemetry, properties and commands. -- An IoT Plug and Play device that implements the [Temperature controller][d-temperature-controller] model. This model defines multiple interfaces: - - The top-level interface defines telemetry, properties and commands. - - The model includes two [Thermostat][thermostat-model] components, and a [device information][d-device-info] component. - -> NOTE: These samples are only meant to demonstrate the usage of Plug and Play APIs. If you are looking for a good device sample to get started with, please see the [device reconnection sample][device-reconnection-sample]. It shows how to connect a device, handle disconnect events, cases to handle when making calls, and when to re-initialize the `DeviceClient`. - -[pnp-convention]: https://docs.microsoft.com/azure/iot-pnp/concepts-convention -[d-thermostat]: ./Thermostat -[d-temperature-controller]: ./TemperatureController -[thermostat-model]: /iot-hub/Samples/device/convention-based-samples/Thermostat/Models/Thermostat.json -[d-device-info]: https://devicemodels.azure.com/dtmi/azure/devicemanagement/deviceinformation-1.json -[thermostat-hub-qs]: https://docs.microsoft.com/azure/iot-pnp/quickstart-connect-device?pivots=programming-language-csharp -[temp-controller-hub-tutorial]: https://docs.microsoft.com/azure/iot-pnp/tutorial-multiple-components?pivots=programming-language-csharp -[temp-controller-central-tutorial]: https://docs.microsoft.com/azure/iot-central/core/tutorial-connect-device?pivots=programming-language-csharp -[device-reconnection-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/DeviceReconnectionSample -[latest-master-release]: https://github.com/Azure/azure-iot-sdk-csharp/tree/2021-05-13 -[latest-preview-release]: https://github.com/Azure/azure-iot-sdk-csharp/tree/preview_2021-6-8 \ No newline at end of file diff --git a/iothub/device/samples/readme.md b/iothub/device/samples/readme.md index 3908583189..0fd55b6269 100644 --- a/iothub/device/samples/readme.md +++ b/iothub/device/samples/readme.md @@ -1,5 +1,10 @@ -This folder contains simple samples showing how to use the various features of Microsoft Azure IoT Hub service, from a device running C# code. +This folder contains simple samples showing how to use the various preview features of Microsoft Azure IoT Hub .NET SDK. + +The following features are currently in preview: +- .NET 5.0 support. +- Support for convention-based operations. +- Device Streaming. ### [Device samples][device-samples] @@ -10,10 +15,9 @@ This folder contains simple samples showing how to use the various features of M - [Receive message sample][d-receive-message-sample] - [Twin sample][d-twin-sample] - [File upload sample][d-file-upload-sample] -- [Import/export devices sample][d-import-export-devices-sample] - [Connect with X509 certificate sample][d-x509-cert-sample] -- [Plug and Play device samples][d-pnp-sample] -- [Xamarin sample][d-xamarin-sample] +- [Convention based operations samples][d-convention-based-operations-sample] +- [Device streaming sample][d-device-streaming-sample] ### Module sample @@ -65,18 +69,17 @@ You need to clone the repository or download the sample (the one you want to try dotnet run ``` -[device-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device -[d-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/DeviceReconnectionSample -[d-receive-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/MessageReceiveSample -[d-method-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/MethodSample -[d-twin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/TwinSample -[d-file-upload-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/FileUploadSample -[d-x509-cert-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/X509DeviceCertWithChainSample -[d-import-export-devices-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/ImportExportDevicesSample -[d-pnp-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/PnpDeviceSamples -[d-xamarin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/XamarinSample - -[m-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/module/ModuleSample +[device-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device +[d-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/DeviceReconnectionSample +[d-receive-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/MessageReceiveSample +[d-method-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/MethodSample +[d-twin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/TwinSample +[d-file-upload-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/FileUploadSample +[d-x509-cert-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/X509DeviceCertWithChainSample +[d-convention-based-operations-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/ConventionBasedOperations +[d-device-streaming-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/device/DeviceStreamingSample + +[m-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/module/ModuleSample [lnk-setup-iot-hub]: https://aka.ms/howtocreateazureiothub [lnk-manage-iot-device]: https://github.com/Azure/azure-iot-device-ecosystem/blob/master/setup_iothub.md#create-new-device-in-the-iot-hub-device-identity-registry \ No newline at end of file From 72b470b1155a77569a1228bb8e307c742c165474 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 30 Jun 2021 19:52:43 -0700 Subject: [PATCH 31/77] fix(e2e-tests): Fix file renaming Configutaion to TestConfiguration --- e2e/test/iothub/command/CommandE2ETests.cs | 4 ++-- e2e/test/iothub/properties/PropertiesE2ETests.cs | 12 ++++++------ .../properties/PropertiesFaultInjectionTests.cs | 2 +- .../properties/PropertiesWithComponentsE2ETests.cs | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/e2e/test/iothub/command/CommandE2ETests.cs b/e2e/test/iothub/command/CommandE2ETests.cs index 02bea0171d..e1f00fcc26 100644 --- a/e2e/test/iothub/command/CommandE2ETests.cs +++ b/e2e/test/iothub/command/CommandE2ETests.cs @@ -70,7 +70,7 @@ public static async Task DigitalTwinsSendCommandAndVerifyResponseAsync(string de int statusCode = 0; #if NET451 - ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Invoke command {commandName}."); @@ -95,7 +95,7 @@ await serviceClient.InvokeDeviceMethodAsync( serviceClient.Dispose(); #else - DigitalTwinClient digitalTwinClient = DigitalTwinClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + DigitalTwinClient digitalTwinClient = DigitalTwinClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); logger.Trace($"{nameof(DigitalTwinsSendCommandAndVerifyResponseAsync)}: Invoke command {commandName}."); diff --git a/e2e/test/iothub/properties/PropertiesE2ETests.cs b/e2e/test/iothub/properties/PropertiesE2ETests.cs index 0a2da2c3c7..7db552aa57 100644 --- a/e2e/test/iothub/properties/PropertiesE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesE2ETests.cs @@ -22,7 +22,7 @@ public class PropertiesE2ETests : E2EMsTestBase { private readonly string _devicePrefix = $"E2E_{nameof(PropertiesE2ETests)}_"; - private static readonly RegistryManager s_registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + private static readonly RegistryManager s_registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); private static readonly TimeSpan s_maxWaitTimeForCallback = TimeSpan.FromSeconds(30); private static readonly Dictionary s_mapOfPropertyValues = new Dictionary @@ -225,7 +225,7 @@ public static async Task Properties_DeviceSetsPropertyAndGetsItBackAsync(Devi public static async Task RegistryManagerUpdateWritablePropertyAsync(string deviceId, string propName, T propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -311,7 +311,7 @@ private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNe string propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var twinPatch = new Twin(); @@ -333,7 +333,7 @@ private async Task Properties_DeviceSetsPropertyAndServiceReceivesItAsync(Client string propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var patch = new ClientPropertyCollection(); @@ -357,7 +357,7 @@ private async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemov string propEmptyValue = "{}"; TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); // First send a property patch with valid values for both prop1 and prop2. @@ -416,7 +416,7 @@ private async Task Properties_ClientHandlesRejectionInvalidPropertyNameAsync(Cli string propName2 = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); Func func = async () => diff --git a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs index aa32c93a20..3c71d594f6 100644 --- a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs +++ b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs @@ -153,7 +153,7 @@ await FaultInjection private async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, string propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; diff --git a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs index 156c67f9fd..96040512d5 100644 --- a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs @@ -24,7 +24,7 @@ public class PropertiesWithComponentsE2ETests : E2EMsTestBase private readonly string _devicePrefix = $"E2E_{nameof(PropertiesWithComponentsE2ETests)}_"; - private static readonly RegistryManager s_registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + private static readonly RegistryManager s_registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); private static readonly TimeSpan s_maxWaitTimeForCallback = TimeSpan.FromSeconds(30); private static readonly Dictionary s_mapOfPropertyValues = new Dictionary @@ -227,7 +227,7 @@ public static async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBac public static async Task RegistryManagerUpdateWritablePropertyAsync(string deviceId, string componentName, string propName, T propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); var componentProperties = new TwinCollection @@ -318,7 +318,7 @@ private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDevice string propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var twinPatch = new Twin(); @@ -345,7 +345,7 @@ private async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceives string propValue = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var patch = new ClientPropertyCollection(); @@ -369,7 +369,7 @@ private async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemov string propEmptyValue = "{}"; TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); // First send a property patch with valid values for both prop1 and prop2. @@ -444,7 +444,7 @@ private async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropert string propName2 = Guid.NewGuid().ToString(); TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); + using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); Func func = async () => From 88ddafd82f73c8e7b6a20a368413cc9aee7132ac Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 2 Jul 2021 10:03:19 -0700 Subject: [PATCH 32/77] fix(iot-device): Update connection string validation param --- iothub/device/src/IotHubConnectionStringBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iothub/device/src/IotHubConnectionStringBuilder.cs b/iothub/device/src/IotHubConnectionStringBuilder.cs index 1b731910c8..f235da764d 100644 --- a/iothub/device/src/IotHubConnectionStringBuilder.cs +++ b/iothub/device/src/IotHubConnectionStringBuilder.cs @@ -289,7 +289,7 @@ private void Validate() ValidateFormat(DeviceId, DeviceIdPropertyName, s_idNameRegex); if (!string.IsNullOrEmpty(ModuleId)) { - ValidateFormat(ModuleId, DeviceIdPropertyName, s_idNameRegex); + ValidateFormat(ModuleId, ModuleIdPropertyName, s_idNameRegex); } ValidateFormatIfSpecified(SharedAccessKeyName, SharedAccessKeyNamePropertyName, s_sharedAccessKeyNameRegex); From a3554490d1b2974e4963b13e01cf135fba6eca37 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Wed, 7 Jul 2021 15:20:00 -0700 Subject: [PATCH 33/77] Update team codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 86b6ad4217..1190b2f777 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # Track1 .NET Azure IoT Hub and DPS SDKs -* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @bikamani @barustum @jamdavi \ No newline at end of file +* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @barustum @jamdavi \ No newline at end of file From 61f2c3f712e1a0337ffc7d49cf2560658d13a767 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 7 Jul 2021 08:24:09 -0700 Subject: [PATCH 34/77] fix(e2e-tests): Update resource generation script to generate resources for samples --- .../E2ETestsSetup/e2eTestsSetup.ps1 | 80 ++++++++++++++++++- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 index 65e1dd9f15..40b45a6647 100644 --- a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 +++ b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 @@ -488,6 +488,12 @@ if ($isVerified -eq 'false') az iot hub certificate verify -g $ResourceGroup --hub-name $iotHubName --name $hubUploadCertificateName -e $etag --path $verificationCertPath --output none } +################################################################################################################################## +# Fetch the iothubowner policy details +################################################################################################################################## +$iothubownerSasPolicy = "iothubowner" +$iothubownerSasPrimaryKey = az iot hub policy show --hub-name $iotHubName --name $iothubownerSasPolicy --query 'primaryKey' + ################################################################################################################################## # Create device in IoTHub that uses a certificate signed by intermediate certificate ################################################################################################################################## @@ -496,10 +502,69 @@ $iotHubCertChainDevice = az iot hub device-identity list -g $ResourceGroup --hub if (-not $iotHubCertChainDevice) { - Write-Host "`nCreating device $iotHubCertChainDeviceCommonName on IoT Hub." + Write-Host "`nCreating X509 CA certificate authenticated device $iotHubCertChainDeviceCommonName on IoT Hub." az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubCertChainDeviceCommonName --am x509_ca } +################################################################################################################################## +# Create the IoT devices and modules that are used by the .NET samples +################################################################################################################################## +$iotHubSasBasedDeviceId = "DoNotDeleteDevice1" +$iotHubSasBasedDevice = az iot hub device-identity list -g $ResourceGroup --hub-name $iotHubName --query "[?deviceId=='$iotHubSasBasedDeviceId'].deviceId" --output tsv + +if (-not $iotHubSasBasedDevice) +{ + Write-Host "`nCreating SAS based device $iotHubSasBasedDeviceId on IoT Hub." + az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubSasBasedDeviceId --ee +} +$iotHubSasBasedDeviceConnectionString = az iot hub device-identity connection-string show --device-id $iotHubSasBasedDeviceId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv + +$iotHubSasBasedModuleId = "DoNotDeleteModule1" +$iotHubSasBasedModule = az iot hub module-identity list -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubSasBasedDeviceId --query "[?moduleId=='$iotHubSasBasedModuleId'].moduleId" --output tsv + +if (-not $iotHubSasBasedModule) +{ + Write-Host "`nCreating SAS based module $iotHubSasBasedModuleId under device $iotHubSasBasedDeviceId on IoT Hub." + az iot hub module-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubSasBasedDeviceId --module-id $iotHubSasBasedModuleId +} +$iotHubSasBasedModuleConnectionString = az iot hub module-identity connection-string show --device-id $iotHubSasBasedDeviceId --module-id $iotHubSasBasedModuleId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv + +$thermostatSampleDeviceId = "ThermostatSample_DoNotDelete" +$thermostatSampleDevice = az iot hub device-identity list -g $ResourceGroup --hub-name $iotHubName --query "[?deviceId=='$thermostatSampleDeviceId'].deviceId" --output tsv + +if (-not $thermostatSampleDevice) +{ + Write-Host "`nCreating SAS based device $thermostatSampleDeviceId on IoT Hub." + az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $thermostatSampleDeviceId --ee +} +$thermostatSampleDeviceConnectionString = az iot hub device-identity connection-string show --device-id $thermostatSampleDeviceId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv + +$temperatureControllerSampleDeviceId = "TemperatureControllerSample_DoNotDelete" +$temperatureControllerSampleDevice = az iot hub device-identity list -g $ResourceGroup --hub-name $iotHubName --query "[?deviceId=='$temperatureControllerSampleDeviceId'].deviceId" --output tsv + +if (-not $temperatureControllerSampleDevice) +{ + Write-Host "`nCreating SAS based device $temperatureControllerSampleDeviceId on IoT Hub." + az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $temperatureControllerSampleDeviceId --ee +} +$temperatureControllerSampleDeviceConnectionString = az iot hub device-identity connection-string show --device-id $temperatureControllerSampleDeviceId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv + +################################################################################################################################## +# Create the DPS enrollments that are used by the .NET samples +################################################################################################################################## + +$symmetricKeySampleEnrollmentRegistrationId = "SymmetricKeySampleIndividualEnrollment" +$symmetricKeyEnrollmentExists = az iot dps enrollment list -g $ResourceGroup --dps-name $dpsName --query "[?deviceId=='$symmetricKeySampleEnrollmentRegistrationId'].deviceId" --output tsv +if ($symmetricKeyEnrollmentExists) +{ + Write-Host "`nDeleting existing individual enrollment $symmetricKeySampleEnrollmentRegistrationId." + az iot dps enrollment delete -g $ResourceGroup --dps-name $dpsName --enrollment-id $symmetricKeySampleEnrollmentRegistrationId +} +Write-Host "`nAdding individual enrollment $symmetricKeySampleEnrollmentRegistrationId." +az iot dps enrollment create -g $ResourceGroup --dps-name $dpsName --enrollment-id $symmetricKeySampleEnrollmentRegistrationId --attestation-type symmetrickey --output none + +$symmetricKeySampleEnrollmentPrimaryKey = az iot dps enrollment show -g $ResourceGroup --dps-name $dpsName --enrollment-id $symmetricKeySampleEnrollmentRegistrationId --show-keys --query 'attestation.symmetricKey.primaryKey' --output tsv + ################################################################################################################################## # Uploading certificate to DPS, verifying and creating enrollment groups ################################################################################################################################## @@ -587,7 +652,7 @@ Remove-Item -r $selfSignedCerts Write-Host "`nWriting secrets to KeyVault $keyVaultName." az keyvault set-policy -g $ResourceGroup --name $keyVaultName --object-id $userObjectId --secret-permissions delete get list set --output none -az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-CONNECTION-STRING" --value $iotHubConnectionString --output none # IoT Hub Connection string Environment variable for Java +az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-CONNECTION-STRING" --value $iotHubConnectionString --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-PFX-X509-THUMBPRINT" --value $iotHubThumbprint --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-PROXY-SERVER-ADDRESS" --value $proxyServerAddress --output none az keyvault secret set --vault-name $keyVaultName --name "FAR-AWAY-IOTHUB-HOSTNAME" --value $farHubHostName --output none @@ -631,6 +696,17 @@ az keyvault secret set --vault-name $keyVaultName --name "PROVISIONING-CONNECTIO az keyvault secret set --vault-name $keyVaultName --name "E2E-IKEY" --value $instrumentationKey --output none +# Below environment variables are used by .NET samples +az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-DEVICE-CONN-STRING" --value $iotHubSasBasedDeviceConnectionString --output none +az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-MODULE-CONN-STRING" --value $iotHubSasBasedModuleConnectionString --output none +az keyvault secret set --vault-name $keyVaultName --name "PNP-TC-DEVICE-CONN-STRING" --value $temperatureControllerSampleDeviceConnectionString --output none +az keyvault secret set --vault-name $keyVaultName --name "PNP-THERMOSTAT-DEVICE-CONN-STRING" --value $thermostatSampleDeviceConnectionString --output none +az keyvault secret set --vault-name $keyVaultName --name "PATH-TO-DEVICE-PREFIX-FOR-DELETION-FILE" --value "csharp_devices_list.csv" --output none +az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-SAS-KEY" --value $iothubownerSasPrimaryKey --output none +az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-SAS-KEY-NAME" --value $iothubownerSasPolicy --output none +az keyvault secret set --vault-name $keyVaultName --name "DPS-SYMMETRIC-KEY-INDIVIDUAL-ENROLLMENT-REGISTRATION-ID" --value $symmetricKeySampleEnrollmentRegistrationId --output none +az keyvault secret set --vault-name $keyVaultName --name "DPS-SYMMETRIC-KEY-INDIVIDUAL-ENROLLEMNT-PRIMARY-KEY" --value $symmetricKeySampleEnrollmentPrimaryKey --output none + ################################################################################################################################### # Run docker containers for TPM simulators and Proxy ################################################################################################################################### From c76a64de272da986b6840251f482249e094a725c Mon Sep 17 00:00:00 2001 From: bikamani <41314966+bikamani@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:50:29 -0700 Subject: [PATCH 35/77] initial changeS (#2060) Co-authored-by: Sindhu Nagesh --- iothub/device/src/InternalClient.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/iothub/device/src/InternalClient.cs b/iothub/device/src/InternalClient.cs index 25a420da3c..b52b5bf0a5 100644 --- a/iothub/device/src/InternalClient.cs +++ b/iothub/device/src/InternalClient.cs @@ -1409,13 +1409,20 @@ private async Task OnDeviceMessageReceivedAsync(Message message) // Grab this semaphore so that there is no chance that the _deviceReceiveMessageCallback instance is set in between the read of the // item1 and the read of the item2 await _deviceReceiveMessageSemaphore.WaitAsync().ConfigureAwait(false); - ReceiveMessageCallback callback = _deviceReceiveMessageCallback?.Item1; - object callbackContext = _deviceReceiveMessageCallback?.Item2; - _deviceReceiveMessageSemaphore.Release(); - if (callback != null) + try + { + ReceiveMessageCallback callback = _deviceReceiveMessageCallback?.Item1; + object callbackContext = _deviceReceiveMessageCallback?.Item2; + + if (callback != null) + { + _ = callback.Invoke(message, callbackContext); + } + } + finally { - await callback.Invoke(message, callbackContext).ConfigureAwait(false); + _deviceReceiveMessageSemaphore.Release(); } if (Logging.IsEnabled) From c1b707c1e183c6fa9936bdafe652761344ace386 Mon Sep 17 00:00:00 2001 From: Sindhu Nagesh Date: Tue, 13 Jul 2021 22:17:42 -0700 Subject: [PATCH 36/77] Adding readme to explain authentication, retry and timeouts in DeviceClient (#2096) --- DeviceConnectionAndReliabilityReadme.md | 85 +++++++++++++++++++ iothub/device/src/AmqpTransportSettings.cs | 15 +++- iothub/device/src/Http1TransportSettings.cs | 5 +- iothub/device/src/ITransportSettings.cs | 2 +- .../Transport/Mqtt/MqttTransportSettings.cs | 6 +- readme.md | 1 + 6 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 DeviceConnectionAndReliabilityReadme.md diff --git a/DeviceConnectionAndReliabilityReadme.md b/DeviceConnectionAndReliabilityReadme.md new file mode 100644 index 0000000000..dcdd357930 --- /dev/null +++ b/DeviceConnectionAndReliabilityReadme.md @@ -0,0 +1,85 @@ +# Azure IoT Device Client .NET SDK + +## Device connection and messaging reliability + +### Overview + +In this document you will find information about: + +- The connection authentication and renewal methods. +- The reconnection logic and retry policies. +- The timeout controls. + +### Connection authentication + +Authentication can be done using one of the following: + +- [SAS tokens for the device](https://docs.microsoft.com/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node#use-sas-tokens-as-a-device) - Using IoT hub [device shared access key](https://docs.microsoft.com/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node#use-a-shared-access-policy-to-access-on-behalf-of-a-device) or [symmetric key](https://docs.microsoft.com/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node#use-a-symmetric-key-in-the-identity-registry) from DPS identity registry +- [x509 certificate](https://docs.microsoft.com/azure/iot-hub/iot-hub-dev-guide-sas#supported-x509-certificates) - Self signed or [CA-signed](https://docs.microsoft.com/azure/iot-hub/iot-hub-x509ca-overview) +- [TPM based authentication](https://azure.microsoft.com/blog/device-provisioning-identity-attestation-with-tpm/) + +Samples: +- IoT hub device shared access key based authentication sample - [DeviceReconnectionSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/iot-hub/Samples/device/DeviceReconnectionSample/DeviceReconnectionSample.cs#L102) +- Device provisioning service symmetric key based authentication sample - [ProvisioningDeviceClientSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/provisioning/Samples/device/SymmetricKeySample/ProvisioningDeviceClientSample.cs#L62) +- x509 based authentication sample using CA-signed certificates - [X509DeviceCertWithChainSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/iot-hub/Samples/device/X509DeviceCertWithChainSample/Program.cs#L43) +- TPM based authentication sample - [ProvisioningDeviceClientSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/provisioning/Samples/device/TpmSample/ProvisioningDeviceClientSample.cs#L49) + +When using SAS tokens, authentication can be done by: + +- Providing the shared access key of the IoT hub and letting the SDK create the SAS tokens by using one of the `CreateFromConnectionString` methods on the [DeviceClient](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceclient). + + If you choose this option, the SDK will create the SAS tokens and renew them before expiry. The default values for time-to-live and renewal buffer can be changed using the `ClientOptions` properties. + + - [SasTokenTimeToLive](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.clientoptions.sastokentimetolive): The suggested time-to-live value for tokens generated for SAS authenticated clients. Default value is 60 minutes. + - [SasTokenRenewalBuffer](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.clientoptions.sastokenrenewalbuffer): The time buffer before expiry when the token should be renewed, expressed as a percentage of the time-to-live. Acceptable values lie between 0 and 100. Default value is 15%. + + > Note: If the shared access policy name is not specified in the connection string, the audience for the token generation will be set by default to - `/devices/` + +- Providing only the shared access signature + + If you only provide the shared access signature, there will never be any renewal handled by the SDK. + +- Providing your own SAS token using [DeviceAuthenticationWithTokenRefresh](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithtokenrefresh) + + If you choose to use `DeviceAuthenticationWithTokenRefresh` to provide your own implementation of token generation, you can provide the time-to-live and time buffer before expiry through the `DeviceAuthenticationWithTokenRefresh` constructor. The `ClientOptions` only apply to other `IAunthenticationMethod` implementations. + +When using x509 certificates, [DeviceAuthenticationWithX509Certificate](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithx509certificate) can be used. The client authentication will be valid until the certificate is valid. Any renewal will have to be done manually and the client needs to be recreated. + +When using TPM based authentication, the [DeviceAuthenticationWithTpm](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithtpm) can be used. TPM based authentication will eventually generate a SAS token but is more secure than using the shared access key of the IoT hub to generate the token. + +### Authentication methods implemented by the SDK + +The different `IAuthenticationMethod` implementations provided by the SDK are: + +- [DeviceAuthenticationWithRegistrySymmetricKey](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithregistrysymmetrickey) - Authentication method that uses the symmetric key associated with the device in the device registry. +- [DeviceAuthenticationWithSharedAccessPolicyKey](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithsharedaccesspolicykey) - Authentication method that uses a shared access policy key. +- [DeviceAuthenticationWithToken](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithtoken) - Authentication method that uses a shared access signature token. +- [DeviceAuthenticationWithTokenRefresh](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithtokenrefresh) - Abstract class that can be implemented to generate a shared access signature token and allows for token refresh. +- [DeviceAuthenticationWithTpm](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithtpm) - Authentication method that uses a shared access signature token generated using TPM and allows for token refresh. +- [DeviceAuthenticationWithX509Certificate](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.deviceauthenticationwithx509certificate) - Authentication method that uses a X.509 certificates. + +### Connection retry logic + +For both AMQP and MQTT, the SDK will try to reconnect anytime there is any network related disruption. The default retry policy does not have a time limit and will follow exponential back-off. + +> Note: The default retry policy has support for jitter, which ensures that if you have N devices that disconnected at the same time, all of them won't start reconnecting with the same delay. + +For more details on the default retry policy and how to override it, see [retry policy documentation](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/devdoc/retrypolicy.md). + +HTTP is a stateless protocol and will work whenever there is network connectivity. + +### Timeout controls + +There are different timeout values that can be configured for the `DeviceClient`/`ModuleClient` based on the protocol. These values are configuarable through the following transport settings that are passed while creating the client. Once the client is created, the settings cannot be changed. The client will need to be recreated with new settings to make changes. + +AMQP timeout settings: + +- [IdleTimeout](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.amqptransportsettings.idletimeout) - The interval that the client establishes with the service, for sending keep-alive pings. The default value is 2 minutes. +- [OperationTimeout](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.amqptransportsettings.operationtimeout) - The time to wait for any operation to complete. The default is 1 minute. +- [OpenTimeout](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.amqptransportsettings.opentimeout) - This value is not used (TODO: Confirm and update) + +MQTT timeout settings: + +- [ConnectArrivalTimeout](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.transport.mqtt.mqtttransportsettings.connectarrivaltimeout) - The time to wait for receiving an acknowledgment for a CONNECT packet. The default is 1 minute. +- [KeepAliveInSeconds](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.transport.mqtt.mqtttransportsettings.keepaliveinseconds) - The interval, in seconds, that the client establishes with the service, for sending keep-alive pings. The default value is 5 minutes. The client will send a ping request 4 times per keep-alive duration set. It will wait for 30 seconds for the ping response, else mark the connection as disconnected. +- [DeviceReceiveAckTimeout](https://docs.microsoft.com/dotnet/api/microsoft.azure.devices.client.transport.mqtt.mqtttransportsettings.devicereceiveacktimeout) - The time a device will wait for an acknowledgment from service. The default is 5 minutes. diff --git a/iothub/device/src/AmqpTransportSettings.cs b/iothub/device/src/AmqpTransportSettings.cs index 272844f6e8..59b56192bf 100644 --- a/iothub/device/src/AmqpTransportSettings.cs +++ b/iothub/device/src/AmqpTransportSettings.cs @@ -115,12 +115,18 @@ public AmqpTransportSettings(TransportType transportType, uint prefetchCount, Am /// /// Specify client-side heartbeat interval. + /// The interval, that the client establishes with the service, for sending keep alive pings. /// The default value is 2 minutes. /// + /// + /// The client will consider the connection as disconnected if the keep alive ping fails. + /// Setting a very low idle timeout value can cause aggressive reconnects, and might not give the + /// client enough time to establish a connection before disconnecting and reconnecting. + /// public TimeSpan IdleTimeout { get; set; } /// - /// The operation timeout + /// The time to wait for any operation to complete. The default is 1 minute. /// public TimeSpan OperationTimeout { @@ -129,8 +135,11 @@ public TimeSpan OperationTimeout } /// - /// The open timeout + /// The open timeout. The default is 1 minute. /// + /// + /// This property is currently unused. + /// public TimeSpan OpenTimeout { get => _openTimeout; @@ -172,7 +181,7 @@ public TransportType GetTransportType() } /// - /// Returns the default current receive timeout + /// The time to wait for a receive operation. The default value is 1 minute. /// public TimeSpan DefaultReceiveTimeout => DefaultOperationTimeout; diff --git a/iothub/device/src/Http1TransportSettings.cs b/iothub/device/src/Http1TransportSettings.cs index b16efc486d..d8d638d30e 100644 --- a/iothub/device/src/Http1TransportSettings.cs +++ b/iothub/device/src/Http1TransportSettings.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.Devices.Client /// public sealed class Http1TransportSettings : ITransportSettings { - private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(60); + private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromMinutes(1); /// /// Initializes a new instance of the class. @@ -40,6 +40,9 @@ public TransportType GetTransportType() /// /// The time to wait for a receive operation. The default value is 1 minute. /// + /// + /// This property is currently unused. + /// public TimeSpan DefaultReceiveTimeout => s_defaultOperationTimeout; /// diff --git a/iothub/device/src/ITransportSettings.cs b/iothub/device/src/ITransportSettings.cs index 045e932eb6..e4fccefdc3 100644 --- a/iothub/device/src/ITransportSettings.cs +++ b/iothub/device/src/ITransportSettings.cs @@ -17,7 +17,7 @@ public interface ITransportSettings TransportType GetTransportType(); /// - /// The default receive timeout. + /// The time to wait for a receive operation. /// TimeSpan DefaultReceiveTimeout { get; } } diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs index 99fe30cbd9..65d46003b9 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs @@ -99,7 +99,7 @@ public bool CertificateRevocationCheck public bool DeviceReceiveAckCanTimeout { get; set; } /// - /// The time a device will wait, for an acknowledgment from service. + /// The time a device will wait for an acknowledgment from service. /// The default is 5 minutes. /// /// @@ -182,12 +182,14 @@ public bool CertificateRevocationCheck public bool CleanSession { get; set; } /// - /// The interval, in seconds, that the client establishes with the service, for sending keep alive pings. + /// The interval, in seconds, that the client establishes with the service, for sending keep-alive pings. /// The default is 300 seconds. /// /// /// The client will send a ping request 4 times per keep-alive duration set. /// It will wait for 30 seconds for the ping response, else mark the connection as disconnected. + /// Setting a very low keep-alive value can cause aggressive reconnects, and might not give the + /// client enough time to establish a connection before disconnecting and reconnecting. /// public int KeepAliveInSeconds { get; set; } diff --git a/readme.md b/readme.md index d0ca4c0a96..97f50a8c89 100644 --- a/readme.md +++ b/readme.md @@ -147,6 +147,7 @@ This repository contains [provisioning service client SDK](./provisioning/servic - [Set up your development environment](./doc/devbox_setup.md) to prepare your development environment as well as how to run the samples on Linux, Windows or other platforms. - [API reference documentation for .NET](https://docs.microsoft.com/dotnet/api/overview/azure/devices?view=azure-dotnet) - [Get Started with IoT Hub using .NET](https://docs.microsoft.com/azure/iot-hub/iot-hub-csharp-csharp-getstarted) +- [Device connection and messaging reliability](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/DeviceConnectionAndReliabilityReadme.md) > Device Explorer is no longer supported. A replacement tool can be found [here](https://github.com/Azure/azure-iot-explorer). From 058035b28961439a6b0cb972ab7f353ccfae318e Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 15 Jul 2021 16:17:11 -0700 Subject: [PATCH 37/77] refactor(iot-device): Rename property add method --- .../devdoc/Convention-based operations.md | 4 ++-- iothub/device/src/ClientPropertyCollection.cs | 20 ++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index 1ffe4c3b6d..25437da767 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -141,13 +141,13 @@ public class ClientProperties : ClientPropertyCollection { public class ClientPropertyCollection : PayloadCollection { public ClientPropertyCollection(); public long Version { get; protected set; } - public void Add(IDictionary properties); public void AddComponentProperties(string componentName, IDictionary properties); public void AddComponentProperty(string componentName, string propertyName, object propertyValue); - public void AddOrUpdate(IDictionary properties); public void AddOrUpdateComponentProperties(string componentName, IDictionary properties); public void AddOrUpdateComponentProperty(string componentName, string propertyName, object propertyValue); + public void AddOrUpdateRootProperties(IDictionary properties); public void AddOrUpdateRootProperty(string propertyName, object propertyValue); + public void AddRootProperties(IDictionary properties); public void AddRootProperty(string propertyName, object propertyValue); public bool Contains(string componentName, string propertyName); public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue); diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 00e2999b9b..e23246d6a9 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -60,7 +60,7 @@ public void AddComponentProperties(string componentName, IDictionary /// /// - /// Adds the values to the collection. + /// Adds the collection of root-level property values to the collection. /// /// /// If the collection already has a key matching a property name supplied this method will throw an . @@ -69,16 +69,10 @@ public void AddComponentProperties(string componentName, IDictionary /// to ensure the correct formatting is applied when the object is serialized. /// - /// - /// This method directly adds the supplied to the collection. - /// For component-level properties, either ensure that you include the component identifier markers {"__t": "c"} as a part of the supplied , - /// or use the convenience method instead. - /// For more information see . - /// /// /// A collection of properties to add. /// is null. - public void Add(IDictionary properties) + public void AddRootProperties(IDictionary properties) { if (properties == null) { @@ -127,7 +121,7 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionary /// /// - /// + /// /// /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. /// @@ -135,15 +129,9 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionary /// to ensure the correct formatting is applied when the object is serialized. /// - /// - /// This method directly adds or updates the supplied to the collection. - /// For component-level properties, either ensure that you include the component identifier markers {"__t": "c"} as a part of the supplied , - /// or use the convenience method instead. - /// For more information see . - /// /// /// A collection of properties to add or update. - public void AddOrUpdate(IDictionary properties) + public void AddOrUpdateRootProperties(IDictionary properties) => properties .ToList() .ForEach(entry => Collection[entry.Key] = entry.Value); From 2d2f1ebdd0d4a3fc79612b32eea90454e07b169c Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Mon, 19 Jul 2021 14:42:49 -0700 Subject: [PATCH 38/77] Last of error codes documentation (#2110) (#2074) refactor(iot-service): Add API example for DeviceAlreadyExists error (#2101) fix(iot-service): Hide unreferenced error code (#2094) a few misc errors (#2092) * a few misc errors * fixup Document ArgumentNull (#2091) Document InvalidOperation and ArgumentInvalid (#2089) Updating exception details for IotHubThrottledException (#2088) refactor(iot-service): Add comments for service returned error codes (#2083) Fix build error (#2087) Updating exception details for IotHubSuspendedException (#2086) Updating exception details for IotHubCommunicationException (#2085) Fix InvalidProtocolVersion documentation. (#2084) More codes doc'd and code cleanup (#2082) * More codes doc'd, code clean up * Update license * Random is static * License header * More codes doc'd, code clean up More updates to exceptions thrown (#2081) More error code doc comments Remove ref to ExponentialBackoff [Error Codes] Update ThrottleBacklogLimitExceeded error code (#2078) [Error Codes] Update PreconditionFailed error description (#2077) * [Error Codes] Update PreconditionFailed error description * Remove space [Error Codes] Updated MessageTooLarge (#2073) * [Error Codes] Updated MessageTooLarge * Removed en-us where seen * Remove en-us * REmove en-us [Error Codes] Updating UnauthorizedException (#2072) * [Error Codes] Updating UnauthorizedException * Remove en-us * Remove en-us * Update UnauthorizedException.cs * Update UnauthorizedException.cs [Error Codes] Updated ServerErrorException (#2071) * [Error Codes] Updated ServerErrorException * Update ServerErrorException.cs * Update ServerErrorException.cs * Update ServerErrorException.cs * Update ServerErrorException.cs * Update ServerErrorException.cs [Error Codes] Updated QuotaExceededException (#2070) * Updated QuotaExceededException classes * Update QuotaExceededException.cs * Update QuotaExceededException.cs Updated ServerBusyException (#2069) [Error Codes] Update PartitionNotFound error code (#2075) * [Error Codes] Update PartitionNotFound error code * remove double lines * remove double lines Obsolete error codes that are not throw by the service (#2079) Fix deprecation messages. Notes for and deprecation of BlobContainerValidationError, BulkRegistryOperationFailure, and JobQuotaExceeded Document errors and remove unreferenced file --- common/src/service/ExceptionHandlingHelper.cs | 221 ++++++++------- common/src/service/HttpClientHelper.cs | 5 +- e2e/test/prerequisites/readme.md | 2 +- iothub/device/src/Common/ErrorCode.cs | 65 ----- ...eviceMaximumQueueDepthExceededException.cs | 4 +- .../DeviceMessageLockLostException.cs | 9 +- .../Exceptions/DeviceNotFoundException.cs | 8 +- .../Exceptions/ExceptionHandlingHelper.cs | 2 +- .../IotHubCommunicationException.cs | 10 +- .../Exceptions/IotHubSuspendedException.cs | 3 +- .../Exceptions/IotHubThrottledException.cs | 6 +- .../Exceptions/MessageTooLargeException.cs | 3 + .../Exceptions/QuotaExceededException.cs | 5 +- .../Common/Exceptions/ServerBusyException.cs | 5 + .../Common/Exceptions/ServerErrorException.cs | 8 +- .../Exceptions/UnauthorizedException.cs | 5 +- iothub/device/src/Edge/TrustBundleProvider.cs | 2 +- .../HttpHsmSignatureProvider.cs | 20 +- .../src/RetryPolicies/ExponentialBackoff.cs | 5 +- ....cs => ExponentialBackoffRetryStrategy.cs} | 55 ++-- .../src/TransientFaultHandling/RetryPolicy.cs | 2 +- .../TransientFaultHandling/RetryStrategy.cs | 92 +++---- .../Transport/AmqpIot/AmqpIotErrorAdapter.cs | 5 +- .../device/tests/ExponentialBackoffTests.cs | 6 +- .../src/Common/Data/AmqpErrorMapper.cs | 181 ------------- .../DeviceMessageLockLostException.cs | 5 +- .../src/Common/Exceptions/ErrorCode.cs | 255 ++++++++++++++++-- .../Common/Exceptions/IotHubAmqpErrorCode.cs | 1 - .../IotHubCommunicationException.cs | 3 +- .../Exceptions/IotHubSuspendedException.cs | 3 +- .../Exceptions/IotHubThrottledException.cs | 6 +- .../Exceptions/MessageTooLargeException.cs | 5 +- .../Exceptions/QuotaExceededException.cs | 5 +- .../Common/Exceptions/ServerBusyException.cs | 7 +- .../Common/Exceptions/ServerErrorException.cs | 7 +- .../Exceptions/UnauthorizedException.cs | 5 + iothub/service/src/Common/TrackingHelper.cs | 4 - iothub/service/src/JobClient/HttpJobClient.cs | 3 +- iothub/service/src/JobStatus.cs | 2 +- 39 files changed, 557 insertions(+), 483 deletions(-) delete mode 100644 iothub/device/src/Common/ErrorCode.cs rename iothub/device/src/TransientFaultHandling/{ExponentialBackoff.cs => ExponentialBackoffRetryStrategy.cs} (66%) diff --git a/common/src/service/ExceptionHandlingHelper.cs b/common/src/service/ExceptionHandlingHelper.cs index 947659eaad..f2943ddb11 100644 --- a/common/src/service/ExceptionHandlingHelper.cs +++ b/common/src/service/ExceptionHandlingHelper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -17,86 +18,77 @@ namespace Microsoft.Azure.Devices { internal class ExceptionHandlingHelper { - public static IDictionary>> GetDefaultErrorMapping() + private static readonly IReadOnlyDictionary>> s_mappings = + new Dictionary>> { - var mappings = new Dictionary>> { - { - HttpStatusCode.NoContent, - async (response) => new DeviceNotFoundException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.NotFound, - async (response) => new DeviceNotFoundException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.Conflict, - async (response) => new DeviceAlreadyExistsException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { HttpStatusCode.BadRequest, async (response) => new ArgumentException( - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) }, - - { - HttpStatusCode.Unauthorized, - async (response) => new UnauthorizedException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.Forbidden, - async (response) => new QuotaExceededException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.PreconditionFailed, - async (response) => new DeviceMessageLockLostException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.RequestEntityTooLarge, - async (response) => new MessageTooLargeException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.InternalServerError, - async (response) => new ServerErrorException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - HttpStatusCode.ServiceUnavailable, - async (response) => new ServerBusyException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - }, - - { - (HttpStatusCode)429, - async (response) => new ThrottlingException( - code: await GetExceptionCodeAsync(response).ConfigureAwait(false), - message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) - } - }; + HttpStatusCode.NoContent, + async (response) => new DeviceNotFoundException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.NotFound, + async (response) => new DeviceNotFoundException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.Conflict, + async (response) => new DeviceAlreadyExistsException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.BadRequest, async (response) => new ArgumentException( + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.Unauthorized, + async (response) => new UnauthorizedException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.Forbidden, + async (response) => new QuotaExceededException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.PreconditionFailed, + async (response) => new DeviceMessageLockLostException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.RequestEntityTooLarge, + async (response) => new MessageTooLargeException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.InternalServerError, + async (response) => new ServerErrorException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + HttpStatusCode.ServiceUnavailable, + async (response) => new ServerBusyException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + }, + { + (HttpStatusCode)429, + async (response) => new ThrottlingException( + code: await GetExceptionCodeAsync(response).ConfigureAwait(false), + message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) + } + }; - return mappings; - } + public static IReadOnlyDictionary>> GetDefaultErrorMapping() => + s_mappings; public static Task GetExceptionMessageAsync(HttpResponseMessage response) { @@ -104,10 +96,10 @@ public static Task GetExceptionMessageAsync(HttpResponseMessage response } /// - /// Get the fully qualified error code from the http response message, if exists + /// Get the fully-qualified error code from the HTTP response message, if exists. /// - /// The http response message - /// The fully qualified error code, or the response status code if no error code was provided. + /// The HTTP response message + /// The fully-qualified error code, or the response status code, if no error code was provided. public static async Task GetExceptionCodeAsync(HttpResponseMessage response) { // First we will attempt to retrieve the error code from the response content. @@ -121,22 +113,67 @@ public static async Task GetExceptionCodeAsync(HttpResponseMessage re // to 'error code' enum mapping, the SDK will check if both values are a match. If so, the SDK will populate the exception with the proper Code. In the case where // there is a mismatch between the error code and the description, the SDK returns ErrorCode.InvalidErrorCode and log a warning. - int errorCode; + int errorCodeValue = (int)ErrorCode.InvalidErrorCode; try { - IoTHubExceptionResult responseContent = JsonConvert - .DeserializeObject(responseContentStr); - Dictionary messageFields = JsonConvert - .DeserializeObject>(responseContent.Message); + IoTHubExceptionResult responseContent = JsonConvert.DeserializeObject(responseContentStr); - if (messageFields != null - && messageFields.TryGetValue(CommonConstants.ErrorCode, out string errorCodeObj)) + try { - errorCode = Convert.ToInt32(errorCodeObj, CultureInfo.InvariantCulture); + Dictionary messageFields = JsonConvert.DeserializeObject>(responseContent.Message); + + if (messageFields != null + && messageFields.TryGetValue(CommonConstants.ErrorCode, out string errorCodeObj)) + { + // The result of TryParse is not being tracked since errorCodeValue has already been initialized to a default value of InvalidErrorCode. + _ = int.TryParse(errorCodeObj, NumberStyles.Any, CultureInfo.InvariantCulture, out errorCodeValue); + } } - else + catch (JsonReaderException ex) { - return ErrorCode.InvalidErrorCode; + if (Logging.IsEnabled) + Logging.Error(null, $"Failed to deserialize error message into a dictionary: {ex}. Message body: '{responseContentStr}.'"); + + // In some scenarios, the error response string is a ';' delimited string with the service-returned error code. + const char errorFieldsDelimiter = ';'; + string[] messageFields = responseContent.Message?.Split(errorFieldsDelimiter); + + if (messageFields != null) + { + foreach (string messageField in messageFields) + { +#if NET451 || NET472 || NETSTANDARD2_0 + if (messageField.IndexOf(CommonConstants.ErrorCode, StringComparison.OrdinalIgnoreCase) >= 0) +#else + if (messageField.Contains(CommonConstants.ErrorCode, StringComparison.OrdinalIgnoreCase)) +#endif + { + const char errorCodeDelimiter = ':'; + +#if NET451 || NET472 || NETSTANDARD2_0 + if (messageField.IndexOf(errorCodeDelimiter) >= 0) +#else + if (messageField.Contains(errorCodeDelimiter)) +#endif + { + string[] errorCodeFields = messageField.Split(errorCodeDelimiter); + if (Enum.TryParse(errorCodeFields[1], out ErrorCode errorCode)) + { + errorCodeValue = (int)errorCode; + } + } + } + break; + } + } + else + { + if (Logging.IsEnabled) + Logging.Error(null, $"Failed to deserialize error message into a dictionary and could not parse ';' delimited string either: {ex}." + + $" Message body: '{responseContentStr}.'"); + + return ErrorCode.InvalidErrorCode; + } } } catch (JsonReaderException ex) @@ -152,7 +189,7 @@ public static async Task GetExceptionCodeAsync(HttpResponseMessage re if (headerErrorCodeString != null && Enum.TryParse(headerErrorCodeString, out ErrorCode headerErrorCode)) { - if ((int)headerErrorCode == errorCode) + if ((int)headerErrorCode == errorCodeValue) { // We have a match. Therefore, return the proper error code. return headerErrorCode; @@ -160,7 +197,7 @@ public static async Task GetExceptionCodeAsync(HttpResponseMessage re if (Logging.IsEnabled) Logging.Error(null, $"There is a mismatch between the error code retrieved from the response content and the response header." + - $"Content error code: {errorCode}. Header error code description: {(int)headerErrorCode}."); + $"Content error code: {errorCodeValue}. Header error code description: {(int)headerErrorCode}."); } return ErrorCode.InvalidErrorCode; diff --git a/common/src/service/HttpClientHelper.cs b/common/src/service/HttpClientHelper.cs index fb17cebcce..a3ac7fccb3 100644 --- a/common/src/service/HttpClientHelper.cs +++ b/common/src/service/HttpClientHelper.cs @@ -45,14 +45,14 @@ internal sealed class HttpClientHelper : IHttpClientHelper public HttpClientHelper( Uri baseAddress, IAuthorizationHeaderProvider authenticationHeaderProvider, - IDictionary>> defaultErrorMapping, + IReadOnlyDictionary>> defaultErrorMapping, TimeSpan timeout, IWebProxy customHttpProxy, int connectionLeaseTimeoutMilliseconds) { _baseAddress = baseAddress; _authenticationHeaderProvider = authenticationHeaderProvider; - _defaultErrorMapping = new ReadOnlyDictionary>>(defaultErrorMapping); + _defaultErrorMapping = defaultErrorMapping; _defaultOperationTimeout = timeout; // We need two types of HttpClients, one with our default operation timeout, and one without. The one without will rely on @@ -924,7 +924,6 @@ internal static HttpMessageHandler CreateDefaultHttpMessageHandler(IWebProxy web #endif #pragma warning restore CA2000 // Dispose objects before losing scope - if (webProxy != DefaultWebProxySettings.Instance) { httpMessageHandler.UseProxy = webProxy != null; diff --git a/e2e/test/prerequisites/readme.md b/e2e/test/prerequisites/readme.md index 91d3e9aea3..edfa8ad4fe 100644 --- a/e2e/test/prerequisites/readme.md +++ b/e2e/test/prerequisites/readme.md @@ -43,7 +43,7 @@ docker run -d --restart unless-stopped --name azure-iot-tpmsim -p 127.0.0.1:2321 Alternatives: -- Stand-alone executable for Windows: https://www.microsoft.com/en-us/download/details.aspx?id=52507 +- Stand-alone executable for Windows: https://www.microsoft.com/download/details.aspx?id=52507 ### Proxy Server diff --git a/iothub/device/src/Common/ErrorCode.cs b/iothub/device/src/Common/ErrorCode.cs deleted file mode 100644 index c7fd41a90b..0000000000 --- a/iothub/device/src/Common/ErrorCode.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Client.Errors -{ - /// - /// Unique code for each instance of DeviceGateway exception that identifies the error condition that caused the failure. - /// - /// - /// These error codes will allow us to do automatic analysis and aggregation of error responses sent from resource provider and frontend. - /// - public enum ErrorCode - { -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - None = -1, - OrchestrationStateInvalid = 100, - OrchestrationRunningOnIotHub = 101, - IotHubNotFoundInDatabase = 102, - NoMatchingResourcePoolFound = 103, - ResourcePoolNotFound = 104, - NoMatchingResourceFound = 105, - MultipleMatchingResourcesFound = 106, - GarbageCollectionFailed = 107, - IotHubUpdateFailed = 108, - InvalidEventHubAccessRight = 109, - - /// - /// Bad Request - /// - AuthorizationRulesExceededQuota = 200, - - InvalidIotHubName = 201, - InvalidOperationId = 202, - IotHubNameNotAvailable = 203, - SystemPropertiesNotAllowed = 204, - - /// - /// Internal Error - /// - IotHubActivationFailed = 300, - - IotHubDeletionFailed = 301, - IotHubExportFailed = 302, - IotHubsExportFailed = 303, - IotHubImportFailed = 304, - IotHubsImportFailed = 305, - WinFabApplicationUpgradeFailed = 306, - WinFabClusterUpgradeFailed = 307, - IotHubInvalidStateTransition = 308, - IotHubStateTransitionNotDefined = 309, - IotHubInvalidProperties = 310, - - /// - /// Not found - /// - KeyNameNotFound = 400, - - /// - /// Internal Warning Range 1000-1299 - /// - WinFabApplicationCleanupNotAttempted = 1000 - -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - } -} diff --git a/iothub/device/src/Common/Exceptions/DeviceMaximumQueueDepthExceededException.cs b/iothub/device/src/Common/Exceptions/DeviceMaximumQueueDepthExceededException.cs index 88b693b4ad..ed80d08fb0 100644 --- a/iothub/device/src/Common/Exceptions/DeviceMaximumQueueDepthExceededException.cs +++ b/iothub/device/src/Common/Exceptions/DeviceMaximumQueueDepthExceededException.cs @@ -8,7 +8,9 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when an attempt to enqueue a message fails because the message queue for the device is already full. + /// This exception actually corresponds to IoTHubQuotaExceeded. For more information on what causes this error + /// and steps to resolve, see . + /// The exception type has not been changed to avoid breaking changes but the inner exception has the correct exception type. /// [Serializable] public sealed class DeviceMaximumQueueDepthExceededException : IotHubException diff --git a/iothub/device/src/Common/Exceptions/DeviceMessageLockLostException.cs b/iothub/device/src/Common/Exceptions/DeviceMessageLockLostException.cs index e296539033..c2c0d80a08 100644 --- a/iothub/device/src/Common/Exceptions/DeviceMessageLockLostException.cs +++ b/iothub/device/src/Common/Exceptions/DeviceMessageLockLostException.cs @@ -8,10 +8,15 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when an attempt to communicate with a device fails because the lock token was lost (if the connection is lost and regained for example). This timeout has the same effect as if the message was abandonned. + /// This exception is thrown when attempting to reject/abandon/complete a cloud-to-device message with a lock + /// token that has already expired. The lock token expires after the lock timeout set by the service, or if your + /// client connection was lost and regained while receiving the message but before you could reject/abandon/complete it. /// /// - /// An abandoned message will be re-enqueued in the per-device queue, and the instance will receive it again. A rejected message will be deleted from the queue and not received again by the device. + /// An abandoned message will be re-enqueued in the per-device/module queue, and the instance will receive it again. + /// A rejected message will be deleted from the queue and not received again by the device. + /// For more information on the cause for this error and how to resolve, see . + /// For more information on cloud-to-device message lifecycle, see . /// [Serializable] public class DeviceMessageLockLostException : IotHubException diff --git a/iothub/device/src/Common/Exceptions/DeviceNotFoundException.cs b/iothub/device/src/Common/Exceptions/DeviceNotFoundException.cs index 32cf197bd0..270f5f8e6a 100644 --- a/iothub/device/src/Common/Exceptions/DeviceNotFoundException.cs +++ b/iothub/device/src/Common/Exceptions/DeviceNotFoundException.cs @@ -8,7 +8,13 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when an attempt to communicate with a device fails because the given device identifier cannot be found. + /// The exception is thrown when the device is disabled and will be used to set the status to device disabled in the + /// connection status handler. This exception also corresponds to the following error codes on operation responses: + /// + /// AmqpErrorCode.NotFound + /// HttpStatusCode.NotFound + /// HttpStatusCode.NoContent + /// /// [Serializable] public sealed class DeviceNotFoundException : IotHubException diff --git a/iothub/device/src/Common/Exceptions/ExceptionHandlingHelper.cs b/iothub/device/src/Common/Exceptions/ExceptionHandlingHelper.cs index 46a984c2fe..aceb772cef 100644 --- a/iothub/device/src/Common/Exceptions/ExceptionHandlingHelper.cs +++ b/iothub/device/src/Common/Exceptions/ExceptionHandlingHelper.cs @@ -24,7 +24,7 @@ public static IDictionary new MessageTooLargeException(await GetExceptionMessageAsync(response).ConfigureAwait(false))); mappings.Add(HttpStatusCode.InternalServerError, async (response) => new ServerErrorException(await GetExceptionMessageAsync(response).ConfigureAwait(false))); mappings.Add(HttpStatusCode.ServiceUnavailable, async (response) => new ServerBusyException(await GetExceptionMessageAsync(response).ConfigureAwait(false))); - mappings.Add((System.Net.HttpStatusCode)429, async (response) => new IotHubThrottledException(await GetExceptionMessageAsync(response).ConfigureAwait(false), null)); + mappings.Add((HttpStatusCode)429, async (response) => new IotHubThrottledException(await GetExceptionMessageAsync(response).ConfigureAwait(false), null)); return mappings; } diff --git a/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs b/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs index 23a445b09b..165433891e 100644 --- a/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs +++ b/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs @@ -7,8 +7,16 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when an attempt to communicate with the IoT Hub service fails. + /// This exception is thrown when an attempt to communicate with the IoT hub service fails due to transient + /// network errors after exhausting all the retries based on the retry policy set on the client or + /// due to operation timeouts. /// + /// + /// By default, the SDK indefinitely retries dropped connections, unless the retry policy is overridden. + /// For more information on the SDK's retry policy and how to override it, see . + /// When the exception is thrown due to operation timeouts, the inner exception will have OperationCanceledException. + /// Retrying operations failed due to timeouts could resolve the error. + /// [Serializable] public sealed class IotHubCommunicationException : IotHubException { diff --git a/iothub/device/src/Common/Exceptions/IotHubSuspendedException.cs b/iothub/device/src/Common/Exceptions/IotHubSuspendedException.cs index 92ef6d7450..9016aff6e6 100644 --- a/iothub/device/src/Common/Exceptions/IotHubSuspendedException.cs +++ b/iothub/device/src/Common/Exceptions/IotHubSuspendedException.cs @@ -8,7 +8,8 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when the IoT Hub has been suspended. + /// This exception is thrown when the IoT hub has been suspended. This is likely due to exceeding Azure + /// spending limits. To resolve the error, check the Azure bill and ensure there are enough credits. /// [Serializable] public class IotHubSuspendedException : IotHubException diff --git a/iothub/device/src/Common/Exceptions/IotHubThrottledException.cs b/iothub/device/src/Common/Exceptions/IotHubThrottledException.cs index 3de839542d..2e4a4ad7a2 100644 --- a/iothub/device/src/Common/Exceptions/IotHubThrottledException.cs +++ b/iothub/device/src/Common/Exceptions/IotHubThrottledException.cs @@ -8,8 +8,12 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when the service requires exponential back-off because it has exceeded the maximum number of allowed active requests. + /// This exception is thrown when the requests to the IoT hub exceed the limits based on the tier of the hub. + /// Retrying with exponential back-off could resolve this error. /// + /// + /// For information on the IoT hub quotas and throttling, see . + /// [Serializable] public sealed class IotHubThrottledException : IotHubException { diff --git a/iothub/device/src/Common/Exceptions/MessageTooLargeException.cs b/iothub/device/src/Common/Exceptions/MessageTooLargeException.cs index 2ee5d1a167..964ae011c2 100644 --- a/iothub/device/src/Common/Exceptions/MessageTooLargeException.cs +++ b/iothub/device/src/Common/Exceptions/MessageTooLargeException.cs @@ -10,6 +10,9 @@ namespace Microsoft.Azure.Devices.Client.Exceptions /// /// The exception that is thrown when an attempt to send a message fails because the length of the message exceeds the maximum size allowed. /// + /// + /// When the message is too large for IoT Hub you will receive this exception. You should attempt to reduce your message size and send again. For more information on message sizes, see IoT Hub quotas and throttling | Other limits + /// [Serializable] public sealed class MessageTooLargeException : IotHubException { diff --git a/iothub/device/src/Common/Exceptions/QuotaExceededException.cs b/iothub/device/src/Common/Exceptions/QuotaExceededException.cs index 6b2755b8b5..f32923ddac 100644 --- a/iothub/device/src/Common/Exceptions/QuotaExceededException.cs +++ b/iothub/device/src/Common/Exceptions/QuotaExceededException.cs @@ -7,8 +7,11 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when an attempt to add a device fails because the maximum number of registered devices has been reached. + /// The exception that is thrown by the device client when the daily message quota for the IoT hub is exceeded. /// + /// + /// To resolve this exception please review the Troubleshoot Quota Exceeded guide. + /// [Serializable] public sealed class QuotaExceededException : IotHubException { diff --git a/iothub/device/src/Common/Exceptions/ServerBusyException.cs b/iothub/device/src/Common/Exceptions/ServerBusyException.cs index 97ea195aee..da72daec63 100644 --- a/iothub/device/src/Common/Exceptions/ServerBusyException.cs +++ b/iothub/device/src/Common/Exceptions/ServerBusyException.cs @@ -9,6 +9,11 @@ namespace Microsoft.Azure.Devices.Client.Exceptions /// /// The exception that is thrown when the IoT Hub is busy. /// + /// + /// This exception typically means the service is unavailable due to high load or an unexpected error and is usually transient. + /// The best course of action is to retry your operation after some time. + /// By default, the SDK will utilize the retry strategy. + /// [Serializable] public sealed class ServerBusyException : IotHubException { diff --git a/iothub/device/src/Common/Exceptions/ServerErrorException.cs b/iothub/device/src/Common/Exceptions/ServerErrorException.cs index 3b907a940d..02ca731e78 100644 --- a/iothub/device/src/Common/Exceptions/ServerErrorException.cs +++ b/iothub/device/src/Common/Exceptions/ServerErrorException.cs @@ -6,8 +6,14 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown when the IoT Hub returned an error code. + /// The exception that is thrown when the IoT Hub returned an internal service error. /// + /// + /// This exception typically means the IoT Hub service has encountered an unexpected error and is usually transient. + /// Please review the 500xxx Internal errors + /// guide for more information. The best course of action is to retry your operation after some time. By default, + /// the SDK will utilize the retry strategy. + /// [Serializable] public sealed class ServerErrorException : IotHubException { diff --git a/iothub/device/src/Common/Exceptions/UnauthorizedException.cs b/iothub/device/src/Common/Exceptions/UnauthorizedException.cs index 1de938b09f..c0e20172c7 100644 --- a/iothub/device/src/Common/Exceptions/UnauthorizedException.cs +++ b/iothub/device/src/Common/Exceptions/UnauthorizedException.cs @@ -7,8 +7,11 @@ namespace Microsoft.Azure.Devices.Client.Exceptions { /// - /// The exception that is thrown if the current operation was not authorized. + /// The exception that is thrown when there is an authorization error. /// + /// + /// This exception means the client is not authorized to use the specified IoT Hub. Please review the 401003 IoTHubUnauthorized guide for more information. + /// [Serializable] public sealed class UnauthorizedException : IotHubException { diff --git a/iothub/device/src/Edge/TrustBundleProvider.cs b/iothub/device/src/Edge/TrustBundleProvider.cs index 949b214559..d7bf525a03 100644 --- a/iothub/device/src/Edge/TrustBundleProvider.cs +++ b/iothub/device/src/Edge/TrustBundleProvider.cs @@ -17,7 +17,7 @@ internal class TrustBundleProvider : ITrustBundleProvider private static readonly ITransientErrorDetectionStrategy s_transientErrorDetectionStrategy = new ErrorDetectionStrategy(); private static readonly RetryStrategy s_transientRetryStrategy = - new TransientFaultHandling.ExponentialBackoff( + new ExponentialBackoffRetryStrategy( retryCount: 3, minBackoff: TimeSpan.FromSeconds(2), maxBackoff: TimeSpan.FromSeconds(30), diff --git a/iothub/device/src/HsmAuthentication/HttpHsmSignatureProvider.cs b/iothub/device/src/HsmAuthentication/HttpHsmSignatureProvider.cs index 98d1c2d27b..64ad119b95 100644 --- a/iothub/device/src/HsmAuthentication/HttpHsmSignatureProvider.cs +++ b/iothub/device/src/HsmAuthentication/HttpHsmSignatureProvider.cs @@ -25,8 +25,11 @@ internal class HttpHsmSignatureProvider : ISignatureProvider private static readonly ITransientErrorDetectionStrategy s_transientErrorDetectionStrategy = new ErrorDetectionStrategy(); - private static readonly RetryStrategy s_transientRetryStrategy = - new TransientFaultHandling.ExponentialBackoff(retryCount: 3, minBackoff: TimeSpan.FromSeconds(2), maxBackoff: TimeSpan.FromSeconds(30), deltaBackoff: TimeSpan.FromSeconds(3)); + private static readonly RetryStrategy s_transientRetryStrategy = new ExponentialBackoffRetryStrategy( + retryCount: 3, + minBackoff: TimeSpan.FromSeconds(2), + maxBackoff: TimeSpan.FromSeconds(30), + deltaBackoff: TimeSpan.FromSeconds(3)); public HttpHsmSignatureProvider(string providerUri, string apiVersion) { @@ -69,7 +72,8 @@ public async Task SignAsync(string moduleId, string generationId, string BaseUrl = HttpClientHelper.GetBaseUrl(_providerUri) }; - SignResponse response = await SignAsyncWithRetryAsync(hsmHttpClient, moduleId, generationId, signRequest).ConfigureAwait(false); + SignResponse response = await SignAsyncWithRetryAsync(hsmHttpClient, moduleId, generationId, signRequest) + .ConfigureAwait(false); return Convert.ToBase64String(response.Digest); } @@ -91,10 +95,16 @@ public async Task SignAsync(string moduleId, string generationId, string } } - private async Task SignAsyncWithRetryAsync(HttpHsmClient hsmHttpClient, string moduleId, string generationId, SignRequest signRequest) + private async Task SignAsyncWithRetryAsync( + HttpHsmClient hsmHttpClient, + string moduleId, + string generationId, + SignRequest signRequest) { var transientRetryPolicy = new RetryPolicy(s_transientErrorDetectionStrategy, s_transientRetryStrategy); - SignResponse response = await transientRetryPolicy.ExecuteAsync(() => hsmHttpClient.SignAsync(_apiVersion, moduleId, generationId, signRequest)).ConfigureAwait(false); + SignResponse response = await transientRetryPolicy + .ExecuteAsync(() => hsmHttpClient.SignAsync(_apiVersion, moduleId, generationId, signRequest)) + .ConfigureAwait(false); return response; } diff --git a/iothub/device/src/RetryPolicies/ExponentialBackoff.cs b/iothub/device/src/RetryPolicies/ExponentialBackoff.cs index dd1d7fc333..e46d149962 100644 --- a/iothub/device/src/RetryPolicies/ExponentialBackoff.cs +++ b/iothub/device/src/RetryPolicies/ExponentialBackoff.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using Microsoft.Azure.Devices.Client.TransientFaultHandling; namespace Microsoft.Azure.Devices.Client { @@ -10,7 +11,7 @@ namespace Microsoft.Azure.Devices.Client /// public class ExponentialBackoff : IRetryPolicy { - private readonly TransientFaultHandling.ExponentialBackoff _exponentialBackoffRetryStrategy; + private readonly ExponentialBackoffRetryStrategy _exponentialBackoffRetryStrategy; /// /// Creates an instance of ExponentialBackoff. @@ -22,7 +23,7 @@ public class ExponentialBackoff : IRetryPolicy public ExponentialBackoff(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) { - _exponentialBackoffRetryStrategy = new TransientFaultHandling.ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff); + _exponentialBackoffRetryStrategy = new ExponentialBackoffRetryStrategy(retryCount, minBackoff, maxBackoff, deltaBackoff); } /// diff --git a/iothub/device/src/TransientFaultHandling/ExponentialBackoff.cs b/iothub/device/src/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs similarity index 66% rename from iothub/device/src/TransientFaultHandling/ExponentialBackoff.cs rename to iothub/device/src/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs index 0813e4c20c..b76c505610 100644 --- a/iothub/device/src/TransientFaultHandling/ExponentialBackoff.cs +++ b/iothub/device/src/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs @@ -1,72 +1,76 @@ -//Copyright(c) Microsoft.All rights reserved. -//Microsoft would like to thank its contributors, a list -//of whom are at http://aka.ms/entlib-contributors +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Microsoft would like to thank its contributors, a list of whom are at http://aka.ms/entlib-contributors using System; -//Licensed under the Apache License, Version 2.0 (the "License"); you -//may not use this file except in compliance with the License. You may -//obtain a copy of the License at +// Source licensed under the Apache License, Version 2.0 (the "License"); you +// may not use this file except in compliance with the License. You may +// obtain a copy of the License at //http://www.apache.org/licenses/LICENSE-2.0 -//Unless required by applicable law or agreed to in writing, software -//distributed under the License is distributed on an "AS IS" BASIS, -//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -//implied. See the License for the specific language governing permissions -//and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing permissions +// and limitations under the License. // THIS FILE HAS BEEN MODIFIED FROM ITS ORIGINAL FORM. // Change Log: // 9/1/2017 jasminel Renamed namespace to Microsoft.Azure.Devices.Client.TransientFaultHandling and modified access modifier to internal. +// 7/12/2021 drwill Renamed class from ExponentialBackoff to ExponentialBackoffRetryStrategy to avoid naming internal conflict. namespace Microsoft.Azure.Devices.Client.TransientFaultHandling { /// /// A retry strategy with back-off parameters for calculating the exponential delay between retries. /// - internal class ExponentialBackoff : RetryStrategy + internal class ExponentialBackoffRetryStrategy : RetryStrategy { - private readonly int _retryCount; + private static readonly Random s_random = new Random(); + private readonly int _retryCount; private readonly TimeSpan _minBackoff; - private readonly TimeSpan _maxBackoff; - private readonly TimeSpan _deltaBackoff; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ExponentialBackoff() : this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff) + public ExponentialBackoffRetryStrategy() + : this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff) { } /// - /// Initializes a new instance of the class with the specified retry settings. + /// Initializes a new instance of the class with the specified retry settings. /// /// The maximum number of retry attempts. /// The minimum back-off time /// The maximum back-off time. /// The value that will be used to calculate a random delta in the exponential delay between retries. - public ExponentialBackoff(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) : this(null, retryCount, minBackoff, maxBackoff, deltaBackoff, RetryStrategy.DefaultFirstFastRetry) + public ExponentialBackoffRetryStrategy(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(null, retryCount, minBackoff, maxBackoff, deltaBackoff, RetryStrategy.DefaultFirstFastRetry) { } /// - /// Initializes a new instance of the class with the specified name and retry settings. + /// Initializes a new instance of the class with the specified name and retry settings. /// /// The name of the retry strategy. /// The maximum number of retry attempts. /// The minimum back-off time /// The maximum back-off time. /// The value that will be used to calculate a random delta in the exponential delay between retries. - public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) : this(name, retryCount, minBackoff, maxBackoff, deltaBackoff, RetryStrategy.DefaultFirstFastRetry) + public ExponentialBackoffRetryStrategy(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(name, retryCount, minBackoff, maxBackoff, deltaBackoff, RetryStrategy.DefaultFirstFastRetry) { } /// - /// Initializes a new instance of the class with the specified name, retry settings, and fast retry option. + /// Initializes a new instance of the class with the specified name, retry settings, and fast retry option. /// /// The name of the retry strategy. /// The maximum number of retry attempts. @@ -74,7 +78,8 @@ public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, Time /// The maximum back-off time. /// The value that will be used to calculate a random delta in the exponential delay between retries. /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. - public ExponentialBackoff(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, bool firstFastRetry) : base(name, firstFastRetry) + public ExponentialBackoffRetryStrategy(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff, bool firstFastRetry) + : base(name, firstFastRetry) { Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); Guard.ArgumentNotNegativeValue(minBackoff.Ticks, "minBackoff"); @@ -97,11 +102,9 @@ public override ShouldRetry GetShouldRetry() { if (currentRetryCount < _retryCount) { - Random random = new Random(); - double exponentialInterval = (Math.Pow(2.0, currentRetryCount) - 1.0) - * random.Next( + * s_random.Next( (int)_deltaBackoff.TotalMilliseconds * 8 / 10, (int)_deltaBackoff.TotalMilliseconds * 12 / 10) + _minBackoff.TotalMilliseconds; diff --git a/iothub/device/src/TransientFaultHandling/RetryPolicy.cs b/iothub/device/src/TransientFaultHandling/RetryPolicy.cs index 5e46d23f65..2131a7cbec 100644 --- a/iothub/device/src/TransientFaultHandling/RetryPolicy.cs +++ b/iothub/device/src/TransientFaultHandling/RetryPolicy.cs @@ -151,7 +151,7 @@ public RetryPolicy( TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) - : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) + : this(errorDetectionStrategy, new ExponentialBackoffRetryStrategy(retryCount, minBackoff, maxBackoff, deltaBackoff)) { } diff --git a/iothub/device/src/TransientFaultHandling/RetryStrategy.cs b/iothub/device/src/TransientFaultHandling/RetryStrategy.cs index c18c5c4eef..a73ef0779e 100644 --- a/iothub/device/src/TransientFaultHandling/RetryStrategy.cs +++ b/iothub/device/src/TransientFaultHandling/RetryStrategy.cs @@ -1,24 +1,26 @@ -//Copyright(c) Microsoft.All rights reserved. -//Microsoft would like to thank its contributors, a list -//of whom are at http://aka.ms/entlib-contributors +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Microsoft would like to thank its contributors, a list of whom are at http://aka.ms/entlib-contributors using System; -//Licensed under the Apache License, Version 2.0 (the "License"); you -//may not use this file except in compliance with the License. You may -//obtain a copy of the License at +// Source licensed under the Apache License, Version 2.0 (the "License"); you +// may not use this file except in compliance with the License. You may +// obtain a copy of the License at //http://www.apache.org/licenses/LICENSE-2.0 -//Unless required by applicable law or agreed to in writing, software -//distributed under the License is distributed on an "AS IS" BASIS, -//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -//implied. See the License for the specific language governing permissions -//and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing permissions +// and limitations under the License. // THIS FILE HAS BEEN MODIFIED FROM ITS ORIGINAL FORM. // Change Log: // 9/1/2017 jasminel Renamed namespace to Microsoft.Azure.Devices.Client.TransientFaultHandling and modified access modifier to internal. +// 7/12/2021 drwill Changed property+backing field to auto-property. namespace Microsoft.Azure.Devices.Client.TransientFaultHandling { @@ -63,73 +65,57 @@ internal abstract class RetryStrategy /// public const bool DefaultFirstFastRetry = true; - private static readonly RetryStrategy s_noRetry = new FixedInterval(0, DefaultRetryInterval); - - private static readonly RetryStrategy s_defaultFixed = new FixedInterval(DefaultClientRetryCount, DefaultRetryInterval); - - private static readonly RetryStrategy s_defaultProgressive = new Incremental(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement); - - private static readonly RetryStrategy s_defaultExponential = new ExponentialBackoff(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff); + /// + /// Initializes a new instance of the class. + /// + /// The name of the retry strategy. + /// + /// True to immediately retry in the first attempt; otherwise, false. + /// The subsequent retries will remain subject to the configured retry interval. + /// + protected RetryStrategy(string name, bool firstFastRetry) + { + Name = name; + FastFirstRetry = firstFastRetry; + } /// /// Returns a default policy that performs no retries, but invokes the action only once. /// - public static RetryStrategy NoRetry => s_noRetry; + public static RetryStrategy NoRetry { get; } = new FixedInterval(0, DefaultRetryInterval); /// - /// Returns a default policy that implements a fixed retry interval configured with the and parameters. - /// The default retry policy treats all caught exceptions as transient errors. + /// Returns a default policy that implements a fixed retry interval configured with the + /// and parameters. The default retry policy treats all caught exceptions as transient errors. /// - public static RetryStrategy DefaultFixed => s_defaultFixed; + public static RetryStrategy DefaultFixed { get; } = new FixedInterval(DefaultClientRetryCount, DefaultRetryInterval); /// - /// Returns a default policy that implements a progressive retry interval configured with the - /// , - /// , + /// Returns a default policy that implements a progressive retry interval configured with the + /// , + /// , /// and parameters. /// The default retry policy treats all caught exceptions as transient errors. /// - public static RetryStrategy DefaultProgressive => s_defaultProgressive; + public static RetryStrategy DefaultProgressive { get; } = new Incremental(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement); /// - /// Returns a default policy that implements a random exponential retry interval configured with the - /// , - /// , - /// , - /// and parameters. + /// Returns a default policy that implements a random exponential retry interval configured with the , + /// , , and parameters. /// The default retry policy treats all caught exceptions as transient errors. /// - public static RetryStrategy DefaultExponential => s_defaultExponential; + public static RetryStrategy DefaultExponential { get; } = new ExponentialBackoffRetryStrategy(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff); /// /// Gets or sets a value indicating whether the first retry attempt will be made immediately, /// whereas subsequent retries will remain subject to the retry interval. /// - public bool FastFirstRetry - { - get; - set; - } + public bool FastFirstRetry { get; set; } /// /// Gets the name of the retry strategy. /// - public string Name - { - get; - private set; - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the retry strategy. - /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. - protected RetryStrategy(string name, bool firstFastRetry) - { - Name = name; - FastFirstRetry = firstFastRetry; - } + public string Name { get; private set; } /// /// Returns the corresponding ShouldRetry delegate. diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotErrorAdapter.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotErrorAdapter.cs index ee524c278e..aa4a9b982b 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotErrorAdapter.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotErrorAdapter.cs @@ -26,7 +26,6 @@ internal static class AmqpIotErrorAdapter public static readonly AmqpSymbol ArgumentError = AmqpIotConstants.Vendor + ":argument-error"; public static readonly AmqpSymbol ArgumentOutOfRangeError = AmqpIotConstants.Vendor + ":argument-out-of-range"; public static readonly AmqpSymbol DeviceContainerThrottled = AmqpIotConstants.Vendor + ":device-container-throttled"; - public static readonly AmqpSymbol PartitionNotFound = AmqpIotConstants.Vendor + ":partition-not-found"; public static readonly AmqpSymbol IotHubSuspended = AmqpIotConstants.Vendor + ":iot-hub-suspended"; public static Exception GetExceptionFromOutcome(Outcome outcome) @@ -240,8 +239,8 @@ public static Exception ToIotHubClientContract(Error error) else if (error.Condition.Equals(AmqpErrorCode.ResourceLimitExceeded)) { // Note: The DeviceMaximumQueueDepthExceededException is not supposed to be thrown here as it is being mapped to the incorrect error code - // Error code 403004 is only applicable to C2D (Service client); see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-troubleshoot-error-403004-devicemaximumqueuedepthexceeded - // Error code 403002 is applicable to D2C (Device client); see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-troubleshoot-error-403002-iothubquotaexceeded + // Error code 403004 is only applicable to C2D (Service client); see https://docs.microsoft.com/azure/iot-hub/iot-hub-troubleshoot-error-403004-devicemaximumqueuedepthexceeded + // Error code 403002 is applicable to D2C (Device client); see https://docs.microsoft.com/azure/iot-hub/iot-hub-troubleshoot-error-403002-iothubquotaexceeded // We have opted not to change the exception type thrown here since it will be a breaking change, alternatively, we are adding the correct exception type // as the inner exception. retException = new DeviceMaximumQueueDepthExceededException( diff --git a/iothub/device/tests/ExponentialBackoffTests.cs b/iothub/device/tests/ExponentialBackoffTests.cs index 8abb193ef5..9d4d756cea 100644 --- a/iothub/device/tests/ExponentialBackoffTests.cs +++ b/iothub/device/tests/ExponentialBackoffTests.cs @@ -16,7 +16,11 @@ public class ExponentialBackoffTests [TestCategory("Unit")] public void ExponentialBackoffDoesNotUnderflow() { - var exponentialBackoff = new TransientFaultHandling.ExponentialBackoff(MAX_RETRY_ATTEMPTS, RetryStrategy.DefaultMinBackoff, RetryStrategy.DefaultMaxBackoff, RetryStrategy.DefaultClientBackoff); + var exponentialBackoff = new ExponentialBackoffRetryStrategy( + MAX_RETRY_ATTEMPTS, + RetryStrategy.DefaultMinBackoff, + RetryStrategy.DefaultMaxBackoff, + RetryStrategy.DefaultClientBackoff); ShouldRetry shouldRetry = exponentialBackoff.GetShouldRetry(); for (int i = 1; i < MAX_RETRY_ATTEMPTS; i++) { diff --git a/iothub/service/src/Common/Data/AmqpErrorMapper.cs b/iothub/service/src/Common/Data/AmqpErrorMapper.cs index b49a00c947..edf1a537b7 100644 --- a/iothub/service/src/Common/Data/AmqpErrorMapper.cs +++ b/iothub/service/src/Common/Data/AmqpErrorMapper.cs @@ -10,187 +10,6 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { internal static class AmqpErrorMapper { - private const int MaxSizeInInfoMap = 32 * 1024; - - public static Tuple GenerateError(Exception ex) - { - if (ex is DeviceNotFoundException deviceNotFoundException) - { - return Tuple.Create(AmqpErrorCode.NotFound.ToString(), deviceNotFoundException.Message, deviceNotFoundException.TrackingId); - } - - if (ex is DeviceAlreadyExistsException deviceAlreadyExistsException) - { - return Tuple.Create(IotHubAmqpErrorCode.DeviceAlreadyExists.ToString(), deviceAlreadyExistsException.Message, deviceAlreadyExistsException.TrackingId); - } - - if (ex is IotHubThrottledException deviceContainerThrottledException) - { - return Tuple.Create(IotHubAmqpErrorCode.DeviceContainerThrottled.ToString(), deviceContainerThrottledException.Message, deviceContainerThrottledException.TrackingId); - } - - if (ex is QuotaExceededException quotaExceededException) - { - return Tuple.Create(IotHubAmqpErrorCode.QuotaExceeded.ToString(), quotaExceededException.Message, quotaExceededException.TrackingId); - } - - if (ex is DeviceMessageLockLostException messageLockLostException) - { - return Tuple.Create(IotHubAmqpErrorCode.MessageLockLostError.ToString(), messageLockLostException.Message, messageLockLostException.TrackingId); - } - - if (ex is MessageTooLargeException deviceMessageTooLargeException) - { - return Tuple.Create(AmqpErrorCode.MessageSizeExceeded.ToString(), deviceMessageTooLargeException.Message, deviceMessageTooLargeException.TrackingId); - } - - if (ex is DeviceMaximumQueueDepthExceededException queueDepthExceededException) - { - return Tuple.Create(AmqpErrorCode.ResourceLimitExceeded.ToString(), queueDepthExceededException.Message, queueDepthExceededException.TrackingId); - } - - if (ex is PreconditionFailedException preconditionFailedException) - { - return Tuple.Create(IotHubAmqpErrorCode.PreconditionFailed.ToString(), preconditionFailedException.Message, preconditionFailedException.TrackingId); - } - - if (ex is IotHubSuspendedException iotHubSuspendedException) - { - return Tuple.Create(IotHubAmqpErrorCode.IotHubSuspended.ToString(), iotHubSuspendedException.Message, iotHubSuspendedException.TrackingId); - } - - return Tuple.Create(AmqpErrorCode.InternalError.ToString(), ex.ToStringSlim(), (string)null); - } - - public static AmqpException ToAmqpException(Exception exception) - { - return ToAmqpException(exception, false); - } - - public static AmqpException ToAmqpException(Exception exception, bool includeStackTrace) - { - Error amqpError = ToAmqpError(exception, includeStackTrace); - return new AmqpException(amqpError); - } - - public static Error ToAmqpError(Exception exception) - { - return ToAmqpError(exception, false); - } - - public static Error ToAmqpError(Exception exception, bool includeStackTrace) - { - if (exception == null) - { - throw new ArgumentNullException(nameof(exception)); - } - - var error = new Error - { - Description = exception.Message - }; - - if (exception is AmqpException) - { - var amqpException = (AmqpException)exception; - error.Condition = amqpException.Error.Condition; - error.Info = amqpException.Error.Info; - } - else if (exception is UnauthorizedAccessException || exception is UnauthorizedException) - { - error.Condition = AmqpErrorCode.UnauthorizedAccess; - } - else if (exception is NotSupportedException) - { - error.Condition = AmqpErrorCode.NotImplemented; - } - else if (exception is DeviceNotFoundException) - { - error.Condition = AmqpErrorCode.NotFound; - } - else if (exception is IotHubNotFoundException) - { - error.Condition = IotHubAmqpErrorCode.IotHubNotFoundError; - } - else if (exception is DeviceMessageLockLostException) - { - error.Condition = IotHubAmqpErrorCode.MessageLockLostError; - } - else if (exception is MessageTooLargeException) - { - error.Condition = AmqpErrorCode.MessageSizeExceeded; - } - else if (exception is DeviceMaximumQueueDepthExceededException) - { - error.Condition = AmqpErrorCode.ResourceLimitExceeded; - } - else if (exception is TimeoutException) - { - error.Condition = IotHubAmqpErrorCode.TimeoutError; - } - else if (exception is InvalidOperationException) - { - error.Condition = AmqpErrorCode.NotAllowed; - } - else if (exception is ArgumentOutOfRangeException) - { - error.Condition = IotHubAmqpErrorCode.ArgumentOutOfRangeError; - } - else if (exception is ArgumentException) - { - error.Condition = IotHubAmqpErrorCode.ArgumentError; - } - else if (exception is PreconditionFailedException) - { - error.Condition = IotHubAmqpErrorCode.PreconditionFailed; - } - else if (exception is IotHubSuspendedException) - { - error.Condition = IotHubAmqpErrorCode.IotHubSuspended; - } - else if (exception is QuotaExceededException) - { - error.Condition = IotHubAmqpErrorCode.QuotaExceeded; - } - else if (exception is TimeoutException) - { - error.Condition = IotHubAmqpErrorCode.TimeoutError; - } - else - { - error.Condition = AmqpErrorCode.InternalError; - error.Description = error.Description; - } - // we will always need this to add trackingId - if (error.Info == null) - { - error.Info = new Fields(); - } - - string stackTrace; - if (includeStackTrace && !string.IsNullOrEmpty(stackTrace = exception.StackTrace)) - { - if (stackTrace.Length > MaxSizeInInfoMap) - { - stackTrace = stackTrace.Substring(0, MaxSizeInInfoMap); - } - - // error.Info came from AmqpException then it contains StackTraceName already. - if (!error.Info.TryGetValue(IotHubAmqpProperty.StackTraceName, out string _)) - { - error.Info.Add(IotHubAmqpProperty.StackTraceName, stackTrace); - } - } - - error.Info.TryGetValue(IotHubAmqpProperty.TrackingId, out string trackingId); -#pragma warning disable CS0618 // Type or member is obsolete only for external dependency. - trackingId = TrackingHelper.CheckAndAddGatewayIdToTrackingId(trackingId); -#pragma warning restore CS0618 // Type or member is obsolete only for external dependency. - error.Info[IotHubAmqpProperty.TrackingId] = trackingId; - - return error; - } - public static Exception GetExceptionFromOutcome(Outcome outcome) { Exception retException; diff --git a/iothub/service/src/Common/Exceptions/DeviceMessageLockLostException.cs b/iothub/service/src/Common/Exceptions/DeviceMessageLockLostException.cs index 21817881eb..7f2e3d6da6 100644 --- a/iothub/service/src/Common/Exceptions/DeviceMessageLockLostException.cs +++ b/iothub/service/src/Common/Exceptions/DeviceMessageLockLostException.cs @@ -7,9 +7,8 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when an attempt to communicate with a device fails - /// because the lock token was lost (if the connection is lost and regained for example). - /// This timeout has the same effect as if the message was abandoned. + /// This exception is not directly returned by the service for ServiceClient operations. However, the status code + /// HttpStatusCode.PreconditionFailed is converted to this exception. /// [Serializable] public class DeviceMessageLockLostException : IotHubException diff --git a/iothub/service/src/Common/Exceptions/ErrorCode.cs b/iothub/service/src/Common/Exceptions/ErrorCode.cs index 5d5947da15..4b1be0838c 100644 --- a/iothub/service/src/Common/Exceptions/ErrorCode.cs +++ b/iothub/service/src/Common/Exceptions/ErrorCode.cs @@ -1,128 +1,345 @@ // 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.ComponentModel; + namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// Error Codes for common IoT hub exceptions. + /// Error codes for common IoT hub response errors. /// public enum ErrorCode { -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + /// + /// Used when the error code returned by the hub is unrecognized. If encountered, please report the issue so it can be added here. + /// InvalidErrorCode = 0, // BadRequest - 400 + + /// + /// The API version used by the SDK is not supported by the IoT hub endpoint used in this connection. + /// + /// Usually this would mean that the region of the hub doesn't yet support the API version. One should + /// consider downgrading to a previous version of the SDK that uses an older API version, or use a hub + /// in a region that supports it. + /// + /// InvalidProtocolVersion = 400001, + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] DeviceInvalidResultCount = 400002, + + /// + /// The client has requested an operation that the hub recognizes as invalid. Check the error message + /// for more information about what is invalid. + /// + // Note: although infrequent, this does appear in logs for "Amqp Message.Properties.To must contain the device identifier". + // and perhaps other cases. InvalidOperation = 400003, + + /// + /// Something in the request payload is invalid. Check the error message for more information about what + /// is invalid. + /// + // Note: one example found in logs is for invalid characters in a twin property name. ArgumentInvalid = 400004, + + /// + /// Something in the payload is unexpectedly null. Check the error message for more information about what is invalid. + /// + // Note: an example suggested is null method payloads, but our client converts null to a JSON null, which is allowed. ArgumentNull = 400005, + + /// + /// Returned by the service if a JSON object provided by this library cannot be parsed, for instance, if the JSON provided for + /// is invalid. + /// IotHubFormatError = 400006, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] DeviceStorageEntitySerializationError = 400007, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] BlobContainerValidationError = 400008, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] ImportWarningExistsError = 400009, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] InvalidSchemaVersion = 400010, + + /// + /// A devices with the same Id was present multiple times in the input request for bulk device registry operations. + /// + /// For more information on bulk registry operations, see . + /// + /// DeviceDefinedMultipleTimes = 400011, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] DeserializationError = 400012, + + /// + /// An error was encountered processing bulk registry operations. + /// + /// As this error is in the 4xx HTTP status code range, the service would have detected a problem with the job + /// request or user input. + /// + /// BulkRegistryOperationFailure = 400013, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] CannotRegisterModuleToModule = 400301, // Unauthorized - 401 + + /// + /// The error is internal to IoT hub and is likely transient. + /// + [Obsolete("This error does should not be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] IotHubNotFound = 401001, + /// + /// The SAS token has expired or IoT hub couldn't authenticate the authentication header, rule, or key. + /// For more information, see . + /// IotHubUnauthorizedAccess = 401002, /// - /// The SAS token has expired or IoT hub couldn't authenticate the authentication header, rule, or key. + /// Unused error code. Service does not return it and neither does the SDK. + /// Replaced by /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] IotHubUnauthorized = 401003, // Forbidden - 403 + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] IotHubSuspended = 403001, /// - /// The daily message quota for the IoT hub is exceeded. + /// Total number of messages on the hub exceeded the allocated quota. + /// + /// Increase units for this hub to increase the quota. + /// For more information on quota, please refer to . + /// /// IotHubQuotaExceeded = 403002, + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] JobQuotaExceeded = 403003, /// - /// The underlying cause is that the number of messages enqueued for the device exceeds the queue limit (50). - /// The most likely reason that you're running into this limit is because you're using HTTPS to receive the message, - /// which leads to continuous polling using ReceiveAsync, resulting in IoT hub throttling the request. + /// The underlying cause is that the number of cloud-to-device messages enqueued for the device exceeds the queue limit. + /// + /// You will need to receive and complete/reject the messages from the device-side before you can enqueue any additional messages. + /// If you want to discard the currently enqueued messages, you can + /// purge your device message queue. + /// For more information on cloud-to-device message operations, see . + /// /// DeviceMaximumQueueDepthExceeded = 403004, + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] IotHubMaxCbsTokenExceeded = 403005, // NotFound - 404 /// - /// The operation failed because the device cannot be found by IoT Hub. The device is either not registered or disabled. + /// The operation failed because the device cannot be found by IoT hub. + /// + /// The device is either not registered or disabled. May be thrown by operations such as + /// . + /// /// DeviceNotFound = 404001, + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] JobNotFound = 404002, - PartitionNotFound = 404003, + + /// + /// The error is internal to IoT hub and is likely transient. + /// + /// For more information, see 503003 PartitionNotFound. + /// + /// + [Obsolete("This error does should not be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] + PartitionNotFound = 503003, + + /// + /// The operation failed because the module cannot be found by IoT hub. + /// + /// The module is either not registered or disabled. May be thrown by operations such as + /// . + /// + /// ModuleNotFound = 404010, // Conflict - 409 /// /// There's already a device with the same device Id in the IoT hub. + /// + /// This can be returned on calling + /// with a device that already exists in the IoT hub. + /// /// DeviceAlreadyExists = 409001, + /// + /// The operation failed because it attempted to add a module to a device when that device already has a module registered to it with the same Id. This issue can be + /// fixed by removing the existing module from the device first with . This error code is only returned from + /// methods like . + /// ModuleAlreadyExistsOnDevice = 409301, - // PreconditionFailed - 412 - PreconditionFailed = 412001, + /// + /// The ETag in the request does not match the ETag of the existing resource, as per RFC7232. + /// + /// The ETag is a mechanism for protecting against the race conditions of multiple clients updating the same resource and overwriting each other. + /// In order to get the up-to-date ETag for a twin, see or + /// . + /// + /// + PreconditionFailed = 412001, // PreconditionFailed - 412 /// + /// If the device tries to complete the message after the lock timeout expires, IoT hub throws this exception. + /// /// When a device receives a cloud-to-device message from the queue (for example, using ReceiveAsync()) /// the message is locked by IoT hub for a lock timeout duration of one minute. - /// If the device tries to complete the message after the lock timeout expires, IoT hub throws this exception. + /// /// + [Obsolete("This error should not be returned to a service application. This is relevant only for a device application.")] + [EditorBrowsable(EditorBrowsableState.Never)] DeviceMessageLockLost = 412002, // RequestEntityTooLarge - 413 + + /// + /// When the message is too large for IoT hub you will receive this error.' + /// + /// You should attempt to reduce your message size and send again. + /// For more information on message sizes, see IoT hub quotas and throttling | Other limits + /// + /// MessageTooLarge = 413001, + /// + /// Too many devices were included in the bulk operation. + /// + /// Check the response for details. + /// For more information, see . + /// + /// TooManyDevices = 413002, + + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] TooManyModulesOnDevice = 413003, // Throttling Exception /// /// IoT hub throttling limits have been exceeded for the requested operation. - /// For more information, + /// For more information, IoT hub quotas and throttling. /// ThrottlingException = 429001, + /// + /// IoT hub throttling limits have been exceeded for the requested operation. + /// + /// For more information, see IoT hub quotas and throttling. + /// + /// ThrottleBacklogLimitExceeded = 429002, - InvalidThrottleParameter = 429003, + + /// + /// IoT hub ran into a server side issue when attempting to throttle. + /// + /// For more information, see 500xxx Internal errors. + /// + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] + InvalidThrottleParameter = 500009, // InternalServerError - 500 /// /// IoT hub ran into a server side issue. + /// /// There can be a number of causes for a 500xxx error response. In all cases, the issue is most likely transient. - /// IoT hub nodes can occasionally experience transient faults. When your device tries to connect to a node that is - /// having issues, you receive this error. To mitigate 500xxx errors, issue a retry from the device. + /// IoT hub nodes can occasionally experience transient faults. When your application tries to connect to a node that is + /// having issues, you receive this error. To mitigate 500xxx errors, issue a retry from your application. + /// /// ServerError = 500001, + /// + /// Unused error code. Service does not return it and neither does the SDK. + /// + [Obsolete("This error does not appear to be returned by the service.")] + [EditorBrowsable(EditorBrowsableState.Never)] JobCancelled = 500002, // ServiceUnavailable /// - /// IoT hub encountered an internal error. + /// IoT hub is currently unable to process the request. This is a transient, retryable error. /// ServiceUnavailable = 503001, - -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } } diff --git a/iothub/service/src/Common/Exceptions/IotHubAmqpErrorCode.cs b/iothub/service/src/Common/Exceptions/IotHubAmqpErrorCode.cs index 419567a58d..1ef9db4f6d 100644 --- a/iothub/service/src/Common/Exceptions/IotHubAmqpErrorCode.cs +++ b/iothub/service/src/Common/Exceptions/IotHubAmqpErrorCode.cs @@ -26,7 +26,6 @@ internal static class IotHubAmqpErrorCode public static readonly AmqpSymbol DeviceAlreadyExists = AmqpConstants.Vendor + ":device-already-exists"; public static readonly AmqpSymbol DeviceContainerThrottled = AmqpConstants.Vendor + ":device-container-throttled"; public static readonly AmqpSymbol QuotaExceeded = AmqpConstants.Vendor + ":quota-exceeded"; - public static readonly AmqpSymbol PartitionNotFound = AmqpConstants.Vendor + ":partition-not-found"; public static readonly AmqpSymbol PreconditionFailed = AmqpConstants.Vendor + ":precondition-failed"; public static readonly AmqpSymbol IotHubSuspended = AmqpConstants.Vendor + ":iot-hub-suspended"; } diff --git a/iothub/service/src/Common/Exceptions/IotHubCommunicationException.cs b/iothub/service/src/Common/Exceptions/IotHubCommunicationException.cs index 82874f6dd4..8b6c88de2f 100644 --- a/iothub/service/src/Common/Exceptions/IotHubCommunicationException.cs +++ b/iothub/service/src/Common/Exceptions/IotHubCommunicationException.cs @@ -7,7 +7,8 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when an attempt to communicate with the IoT Hub fails. + /// This exception is thrown when an attempt to communicate with the IoT hub service fails due to transient + /// network issues or operation timeouts. Retrying failed operations could resolve the error. /// [Serializable] public sealed class IotHubCommunicationException : IotHubException diff --git a/iothub/service/src/Common/Exceptions/IotHubSuspendedException.cs b/iothub/service/src/Common/Exceptions/IotHubSuspendedException.cs index 190f79b886..43fb7c0b29 100644 --- a/iothub/service/src/Common/Exceptions/IotHubSuspendedException.cs +++ b/iothub/service/src/Common/Exceptions/IotHubSuspendedException.cs @@ -7,7 +7,8 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when a request is made against an IoT Hub that has been suspended. + /// This exception is thrown when the IoT hub has been suspended. This is likely due to exceeding Azure + /// spending limits. To resolve the error, check the Azure bill and ensure there are enough credits. /// [Serializable] public class IotHubSuspendedException : IotHubException diff --git a/iothub/service/src/Common/Exceptions/IotHubThrottledException.cs b/iothub/service/src/Common/Exceptions/IotHubThrottledException.cs index 91642d653c..5f4b90def0 100644 --- a/iothub/service/src/Common/Exceptions/IotHubThrottledException.cs +++ b/iothub/service/src/Common/Exceptions/IotHubThrottledException.cs @@ -7,8 +7,12 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when the rate of incoming requests exceeds the throttling limit set by IoT Hub. + /// This exception is thrown when the requests to the IoT hub exceed the limits based on the tier of the hub. + /// Retrying with exponential back-off could resolve this error. /// + /// + /// For information on the IoT hub quotas and throttling, see . + /// [Serializable] public sealed class IotHubThrottledException : IotHubException { diff --git a/iothub/service/src/Common/Exceptions/MessageTooLargeException.cs b/iothub/service/src/Common/Exceptions/MessageTooLargeException.cs index 9f61742049..6d3ebcdf42 100644 --- a/iothub/service/src/Common/Exceptions/MessageTooLargeException.cs +++ b/iothub/service/src/Common/Exceptions/MessageTooLargeException.cs @@ -7,8 +7,11 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when a message is sent to IoT Hub that exceeds the maximum allowed bytes in size. + /// The exception that is thrown when an attempt to send a message fails because the length of the message exceeds the maximum size allowed. /// + /// + /// When the message is too large for IoT Hub you will receive this exception. You should attempt to reduce your message size and send again. For more information on message sizes, see IoT Hub quotas and throttling | Other limits + /// [Serializable] public sealed class MessageTooLargeException : IotHubException { diff --git a/iothub/service/src/Common/Exceptions/QuotaExceededException.cs b/iothub/service/src/Common/Exceptions/QuotaExceededException.cs index 8b005c8407..09032c2172 100644 --- a/iothub/service/src/Common/Exceptions/QuotaExceededException.cs +++ b/iothub/service/src/Common/Exceptions/QuotaExceededException.cs @@ -7,8 +7,11 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when the allocated quota set by IoT Hub is exceeded. + /// The exception that is thrown by the service client when the daily message quota for the IoT hub is exceeded. /// + /// + /// To resolve this exception please review the Troubleshoot Quota Exceeded guide. + /// [Serializable] public sealed class QuotaExceededException : IotHubException { diff --git a/iothub/service/src/Common/Exceptions/ServerBusyException.cs b/iothub/service/src/Common/Exceptions/ServerBusyException.cs index 5f06accc1c..940c8f43c0 100644 --- a/iothub/service/src/Common/Exceptions/ServerBusyException.cs +++ b/iothub/service/src/Common/Exceptions/ServerBusyException.cs @@ -7,9 +7,12 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when IoT Hub is busy with previous requests. - /// Callers should wait a while and retry the operation. + /// The exception that is thrown when the IoT Hub is busy. /// + /// + /// This exception typically means the service is unavailable due to high load or an unexpected error and is usually transient. + /// The best course of action is to retry your operation after some time. + /// [Serializable] public sealed class ServerBusyException : IotHubException { diff --git a/iothub/service/src/Common/Exceptions/ServerErrorException.cs b/iothub/service/src/Common/Exceptions/ServerErrorException.cs index 5ce7c011eb..4cf3c31e1f 100644 --- a/iothub/service/src/Common/Exceptions/ServerErrorException.cs +++ b/iothub/service/src/Common/Exceptions/ServerErrorException.cs @@ -7,8 +7,13 @@ namespace Microsoft.Azure.Devices.Common.Exceptions { /// - /// The exception that is thrown when IoT Hub encounters an error while processing a request. + /// The exception that is thrown when the IoT Hub returned an internal service error. /// + /// + /// This exception typically means the IoT Hub service has encountered an unexpected error and is usually transient. + /// Please review the 500xxx Internal errors + /// guide for more information. The best course of action is to retry your operation after some time. + /// [Serializable] public sealed class ServerErrorException : IotHubException { diff --git a/iothub/service/src/Common/Exceptions/UnauthorizedException.cs b/iothub/service/src/Common/Exceptions/UnauthorizedException.cs index 6eba26f540..38f83ca834 100644 --- a/iothub/service/src/Common/Exceptions/UnauthorizedException.cs +++ b/iothub/service/src/Common/Exceptions/UnauthorizedException.cs @@ -9,6 +9,11 @@ namespace Microsoft.Azure.Devices.Common.Exceptions /// /// The exception that is thrown when there is an authorization error. /// + /// + /// This exception means the client is not authorized to use the specified IoT hub. + /// Please review the 401003 IoTHubUnauthorized + /// guide for more information. + /// [Serializable] public sealed class UnauthorizedException : IotHubException { diff --git a/iothub/service/src/Common/TrackingHelper.cs b/iothub/service/src/Common/TrackingHelper.cs index cbc4e77d4b..e6f6c5f875 100644 --- a/iothub/service/src/Common/TrackingHelper.cs +++ b/iothub/service/src/Common/TrackingHelper.cs @@ -220,10 +220,6 @@ public static ErrorCode GetErrorCodeFromAmqpError(Error ex) { return ErrorCode.DeviceNotFound; } - if (ex.Condition.Equals(IotHubAmqpErrorCode.MessageLockLostError)) - { - return ErrorCode.DeviceMessageLockLost; - } if (ex.Condition.Equals(IotHubAmqpErrorCode.IotHubSuspended)) { return ErrorCode.IotHubSuspended; diff --git a/iothub/service/src/JobClient/HttpJobClient.cs b/iothub/service/src/JobClient/HttpJobClient.cs index 63fe9f3683..88a1d2e0ab 100644 --- a/iothub/service/src/JobClient/HttpJobClient.cs +++ b/iothub/service/src/JobClient/HttpJobClient.cs @@ -8,10 +8,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; using Microsoft.Azure.Devices.Common; using Microsoft.Azure.Devices.Common.Exceptions; -using System.Diagnostics.CodeAnalysis; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices { diff --git a/iothub/service/src/JobStatus.cs b/iothub/service/src/JobStatus.cs index aacc7140e5..11a46cbd6b 100644 --- a/iothub/service/src/JobStatus.cs +++ b/iothub/service/src/JobStatus.cs @@ -56,7 +56,7 @@ public enum JobStatus Scheduled, /// - /// Indicates that a Job is in the queue for execution (synonym for enqueued to be depricated) + /// Indicates that a Job is in the queue for execution (synonym for enqueued to be deprecated) /// [EnumMember(Value = "queued")] Queued From 762db48da91716a37d8007d55235d9459a59aed3 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Mon, 19 Jul 2021 15:26:52 -0700 Subject: [PATCH 39/77] Rename readme per style and update reference (#2113) --- azureiot.sln | 4 ++++ ...tyReadme.md => device_connection_and_reliability_readme.md | 0 readme.md | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) rename DeviceConnectionAndReliabilityReadme.md => device_connection_and_reliability_readme.md (100%) diff --git a/azureiot.sln b/azureiot.sln index dd21735d2b..eb9c95bcad 100644 --- a/azureiot.sln +++ b/azureiot.sln @@ -72,6 +72,10 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{719D18A7-E943-461B-B777-0AAEC43916F5}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + configure_tls_protocol_version_and_ciphers.md = configure_tls_protocol_version_and_ciphers.md + device_connection_and_reliability_readme.md = device_connection_and_reliability_readme.md + readme.md = readme.md + supported_platforms.md = supported_platforms.md test.runsettings = test.runsettings EndProjectSection EndProject diff --git a/DeviceConnectionAndReliabilityReadme.md b/device_connection_and_reliability_readme.md similarity index 100% rename from DeviceConnectionAndReliabilityReadme.md rename to device_connection_and_reliability_readme.md diff --git a/readme.md b/readme.md index 97f50a8c89..e35f4cb7f0 100644 --- a/readme.md +++ b/readme.md @@ -147,7 +147,7 @@ This repository contains [provisioning service client SDK](./provisioning/servic - [Set up your development environment](./doc/devbox_setup.md) to prepare your development environment as well as how to run the samples on Linux, Windows or other platforms. - [API reference documentation for .NET](https://docs.microsoft.com/dotnet/api/overview/azure/devices?view=azure-dotnet) - [Get Started with IoT Hub using .NET](https://docs.microsoft.com/azure/iot-hub/iot-hub-csharp-csharp-getstarted) -- [Device connection and messaging reliability](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/DeviceConnectionAndReliabilityReadme.md) +- [Device connection and messaging reliability](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/device_connection_and_reliability_readme.md) > Device Explorer is no longer supported. A replacement tool can be found [here](https://github.com/Azure/azure-iot-explorer). From 22fb8659ef3c96fe7f9ced8dfcb2ac2147b71530 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 19 Jul 2021 17:05:05 -0700 Subject: [PATCH 40/77] * fix(iot-device): TryGetValue methods should check for the component identifier when applicable and should not throw any exceptions (#2111) --- iothub/device/src/ClientPropertyCollection.cs | 74 ++++++--- iothub/device/src/PayloadCollection.cs | 32 ++-- .../tests/ClientPropertyCollectionTests.cs | 149 ++++++++++++++++-- ...ClientPropertyCollectionTestsNewtonsoft.cs | 85 ++++++++++ 4 files changed, 298 insertions(+), 42 deletions(-) diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index e23246d6a9..214889e2ac 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -170,8 +170,9 @@ public bool Contains(string componentName, string propertyName) /// The type to cast the object to. /// The component which holds the required property. /// The property to get. - /// The value of the component-level property. - /// true if the property collection contains a component level property with the specified key; otherwise, false. + /// When this method returns true, this contains the value of the component-level property. + /// When this method returns false, this contains the default value of the type T passed in. + /// True if a component-level property of type T with the specified key was found; otherwise, it returns false. public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue) { if (Logging.IsEnabled && Convention == null) @@ -180,35 +181,72 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou $"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue)); } + // If either the component name or the property name is null, empty or whitespace, + // then return false with the default value of the type passed in. + if (string.IsNullOrWhiteSpace(componentName) || string.IsNullOrWhiteSpace(propertyName)) + { + propertyValue = default; + return false; + } + if (Contains(componentName, propertyName)) { object componentProperties = Collection[componentName]; + // If the ClientPropertyCollection was constructed by the user application (eg. for updating the client properties) + // then the componentProperties are retrieved as a dictionary. + // The required property value can be fetched from the dictionary directly. if (componentProperties is IDictionary nestedDictionary) { - if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement)) + // First verify that the retrieved dictionary contains the component identifier { "__t": "c" }. + // If not, then the retrieved nested dictionary is actually a root-level property of type map. + if (nestedDictionary.TryGetValue(ConventionBasedConstants.ComponentIdentifierKey, out object componentIdentifierValue) + && componentIdentifierValue.ToString() == ConventionBasedConstants.ComponentIdentifierValue) { - // If the value is null, go ahead and return it. - if (dictionaryElement == null) - { - propertyValue = default; - return true; - } - - // If the object is of type T or can be cast to type T, go ahead and return it. - if (dictionaryElement is T valueRef - || NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) + if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement)) { - propertyValue = valueRef; - return true; + // If the value associated with the key is null, then return true with the default value of the type passed in. + if (dictionaryElement == null) + { + propertyValue = default; + return true; + } + + // If the object is of type T or can be cast to type T, go ahead and return it. + if (dictionaryElement is T valueRef + || NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) + { + propertyValue = valueRef; + return true; + } } } } else { - // If it's not, we need to try to convert it using the serializer. - Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); - return true; + // If the ClientPropertyCollection was constructed by the SDK (eg. when retrieving the client properties) + // then the componentProperties are retrieved as the json object that is defined in the PayloadConvention. + // The required property value then needs to be deserialized accordingly. + try + { + // First verify that the retrieved dictionary contains the component identifier { "__t": "c" }. + // If not, then the retrieved nested dictionary is actually a root-level property of type map. + if (Convention + .PayloadSerializer + .TryGetNestedObjectValue(componentProperties, ConventionBasedConstants.ComponentIdentifierKey, out string componentIdentifierValue) + && componentIdentifierValue == ConventionBasedConstants.ComponentIdentifierValue) + { + // Since the value cannot be cast to directly, we need to try to convert it using the serializer. + // If it can be successfully converted, go ahead and return it. + Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); + return true; + } + } + catch + { + // In case the value cannot be converted using the serializer, + // then return false with the default value of the type passed in. + } } } diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index 1943264358..a6ab45581a 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -107,13 +107,11 @@ public bool Contains(string key) /// /// Gets the value of the object from the collection. /// - /// - /// This class is used for both sending and receiving properties for the device. - /// /// The type to cast the object to. /// The key of the property to get. - /// The value of the object from the collection. - /// True if the collection contains an element with the specified key; otherwise, it returns false. + /// When this method returns true, this contains the value of the object from the collection. + /// When this method returns false, this contains the default value of the type T passed in. + /// True if a value of type T with the specified key was found; otherwise, it returns false. public bool TryGetValue(string key, out T value) { if (Logging.IsEnabled && Convention == null) @@ -122,9 +120,16 @@ public bool TryGetValue(string key, out T value) $"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue)); } + // If the key is null, empty or whitespace, then return false with the default value of the type passed in. + if (string.IsNullOrWhiteSpace(key)) + { + value = default; + return false; + } + if (Collection.ContainsKey(key)) { - // If the value is null, go ahead and return it. + // If the value associated with the key is null, then return true with the default value of the type passed in. if (Collection[key] == null) { value = default; @@ -139,9 +144,18 @@ public bool TryGetValue(string key, out T value) return true; } - // If it's not, we need to try to convert it using the serializer. - value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); - return true; + try + { + // If the value cannot be cast to directly, we need to try to convert it using the serializer. + // If it can be successfully converted, go ahead and return it. + value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); + return true; + } + catch + { + // In case the value cannot be converted using the serializer, + // then return false with the default value of the type passed in. + } } value = default; diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index f9ea6caa2e..125b792bd2 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -56,6 +56,7 @@ public class ClientPropertyCollectionTests [TestMethod] public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection { { StringPropertyName, StringPropertyValue }, @@ -70,6 +71,8 @@ public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceC { DateTimePropertyName, s_dateTimePropertyValue } }; + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -107,26 +110,35 @@ public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceC [TestMethod] public void ClientPropertyCollection_AddSimpleObjectAgainThrowsException() { + // arrange var clientProperties = new ClientPropertyCollection { { StringPropertyName, StringPropertyValue } }; + // act Action act = () => clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + + // assert act.Should().Throw("\"Add\" method does not support adding a key that already exists in the collection."); } [TestMethod] public void ClientPropertyCollection_CanUpdateSimpleObjectAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection { { StringPropertyName, StringPropertyValue } }; + + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string outValue); outValue.Should().Be(StringPropertyValue); clientProperties.AddOrUpdateRootProperty(StringPropertyName, UpdatedPropertyValue); + clientProperties.TryGetValue(StringPropertyName, out string outValueChanged); outValueChanged.Should().Be(UpdatedPropertyValue, "\"AddOrUpdate\" should overwrite the value if the key already exists in the collection."); } @@ -134,10 +146,12 @@ public void ClientPropertyCollection_CanUpdateSimpleObjectAndGetBackWithoutDevic [TestMethod] public void ClientPropertyCollection_CanAddNullPropertyAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); clientProperties.AddRootProperty(IntPropertyName, null); + // act, assert clientProperties.TryGetValue(StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -149,10 +163,13 @@ public void ClientPropertyCollection_CanAddNullPropertyAndGetBackWithoutDeviceCl [TestMethod] public void ClientPropertyCollection_CanAddMultiplePropertyAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); clientProperties.AddRootProperty(IntPropertyName, IntPropertyValue); + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -160,24 +177,57 @@ public void ClientPropertyCollection_CanAddMultiplePropertyAndGetBackWithoutDevi outIntValue.Should().Be(IntPropertyValue); } + [TestMethod] + public void ClientPropertyCollection_TryGetValueShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(IntPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueShouldReturnFalseIfValueCouldNotBeDeserialized() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(StringPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + [TestMethod] public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWithoutDeviceClient() { - var clientProperties = new ClientPropertyCollection + // arrange + var componentLevelProperties = new Dictionary { - { ComponentName, new Dictionary { - { StringPropertyName, StringPropertyValue }, - { BoolPropertyName, BoolPropertyValue }, - { DoublePropertyName, DoublePropertyValue }, - { FloatPropertyName, FloatPropertyValue }, - { IntPropertyName, IntPropertyValue }, - { ShortPropertyName, ShortPropertyValue }, - { ObjectPropertyName, s_objectPropertyValue }, - { ArrayPropertyName, s_arrayPropertyValue }, - { MapPropertyName, s_mapPropertyValue }, - { DateTimePropertyName, s_dateTimePropertyValue } } - } + { StringPropertyName, StringPropertyValue }, + { BoolPropertyName, BoolPropertyValue }, + { DoublePropertyName, DoublePropertyValue }, + { FloatPropertyName, FloatPropertyValue }, + { IntPropertyName, IntPropertyValue }, + { ShortPropertyName, ShortPropertyValue }, + { ObjectPropertyName, s_objectPropertyValue }, + { ArrayPropertyName, s_arrayPropertyValue }, + { MapPropertyName, s_mapPropertyValue }, + { DateTimePropertyName, s_dateTimePropertyValue } }; + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperties(ComponentName, componentLevelProperties); + + // act, assert clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -216,19 +266,26 @@ public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWi [TestMethod] public void ClientPropertyCollection_AddSimpleObjectWithComponentAgainThrowsException() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + // act Action act = () => clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + // assert act.Should().Throw("\"Add\" method does not support adding a key that already exists in the collection."); } [TestMethod] public void ClientPropertyCollection_CanUpdateSimpleObjectWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); outValue.Should().Be(StringPropertyValue); @@ -240,10 +297,13 @@ public void ClientPropertyCollection_CanUpdateSimpleObjectWithComponentAndGetBac [TestMethod] public void ClientPropertyCollection_CanAddNullPropertyWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); clientProperties.AddComponentProperty(ComponentName, IntPropertyName, null); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -255,10 +315,13 @@ public void ClientPropertyCollection_CanAddNullPropertyWithComponentAndGetBackWi [TestMethod] public void ClientPropertyCollection_CanAddMultiplePropertyWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); clientProperties.AddComponentProperty(ComponentName, IntPropertyName, IntPropertyValue); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -269,12 +332,15 @@ public void ClientPropertyCollection_CanAddMultiplePropertyWithComponentAndGetBa [TestMethod] public void ClientPropertyCollection_CanAddSimpleWritablePropertyAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); - var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, CommonClientResponseCodes.OK, 2, WritablePropertyDescription); clientProperties.AddRootProperty(StringPropertyName, writableResponse); + // act clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(writableResponse.Value); outValue.AckCode.Should().Be(writableResponse.AckCode); outValue.AckVersion.Should().Be(writableResponse.AckVersion); @@ -284,12 +350,15 @@ public void ClientPropertyCollection_CanAddSimpleWritablePropertyAndGetBackWitho [TestMethod] public void ClientPropertyCollection_CanAddWritablePropertyWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); - var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, CommonClientResponseCodes.OK, 2, WritablePropertyDescription); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, writableResponse); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(writableResponse.Value); outValue.AckCode.Should().Be(writableResponse.AckCode); outValue.AckVersion.Should().Be(writableResponse.AckVersion); @@ -299,15 +368,65 @@ public void ClientPropertyCollection_CanAddWritablePropertyWithComponentAndGetBa [TestMethod] public void ClientPropertyCollection_AddingComponentAddsComponentIdentifier() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); + // assert outValue.Should().Be(StringPropertyValue); componentOut.Should().Be(ConventionBasedConstants.ComponentIdentifierValue); } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, IntPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIfValueCouldNotBeDeserialized() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, StringPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIfNotAComponent() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(MapPropertyName, s_mapPropertyValue); + string incorrectlyMappedComponentName = MapPropertyName; + string incorrectlyMappedComponentPropertyName = "key1"; + + // act + bool isValueRetrieved = clientProperties.TryGetValue(incorrectlyMappedComponentName, incorrectlyMappedComponentPropertyName, out object propertyValue); + + // assert + isValueRetrieved.Should().BeFalse(); + propertyValue.Should().Be(default); + } } internal class CustomClientProperty diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index 07a820febe..7e12e2c9b8 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -121,8 +121,11 @@ public class ClientPropertyCollectionTestsNewtonsoft [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetValue() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -162,8 +165,11 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValue() [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -203,9 +209,13 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGetBack() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyToRoundTrip, DefaultPayloadConvention.Instance); + // act clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(StringPropertyValue); outValue.AckCode.Should().Be(s_writablePropertyResponse.AckCode); outValue.AckVersion.Should().Be(s_writablePropertyResponse.AckVersion); @@ -215,9 +225,13 @@ public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGe [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithComponentAndGetBack() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(StringPropertyValue); outValue.AckCode.Should().Be(s_writablePropertyResponse.AckCode); outValue.AckVersion.Should().Be(s_writablePropertyResponse.AckVersion); @@ -227,14 +241,85 @@ public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithCompone [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetComponentIdentifier() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); + // assert outValue.Should().Be(StringPropertyValue); componentOut.Should().Be(ConventionBasedConstants.ComponentIdentifierValue); } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue("thisPropertyDoesNotExist", out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, "thisPropertyDoesNotExist", out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfValueCouldNotBeDeserialized() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + + bool isValueRetrieved = clientProperties.TryGetValue(StringPropertyName, out int outIntValue); + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfValueCouldNotBeDeserialized() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, StringPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfNotAComponent() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + string incorrectlyMappedComponentName = MapPropertyName; + string incorrectlyMappedComponentPropertyName = "key1"; + + // act + bool isValueRetrieved = clientProperties.TryGetValue(incorrectlyMappedComponentName, incorrectlyMappedComponentPropertyName, out object propertyValue); + + // assert + isValueRetrieved.Should().BeFalse(); + propertyValue.Should().Be(default); + } } internal class RootLevelProperties From 35f8f574d161293b93d4748ff5867686d1971c69 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 20 Jul 2021 13:26:31 -0700 Subject: [PATCH 41/77] refactor(iot-device): Separate client reported properties into a separate accessor --- e2e/test/Helpers/TestDeviceCallbackHandler.cs | 2 +- .../iothub/properties/PropertiesE2ETests.cs | 12 +++---- .../PropertiesFaultInjectionTests.cs | 2 +- .../PropertiesWithComponentsE2ETests.cs | 12 +++---- iothub/device/src/ClientProperties.cs | 31 ++++++++++++------- iothub/device/src/ClientTwinProperties.cs | 6 ++-- .../Common/UrlEncodedDictionarySerializer.cs | 2 +- .../DeviceClient.ConventionBasedOperations.cs | 4 +-- ...nternalClient.ConventionBasedOperations.cs | 2 +- .../ModuleClient.ConventionBasedOperations.cs | 4 +-- iothub/device/src/PayloadCollection.cs | 15 --------- iothub/device/tests/ClientPropertiesTests.cs | 6 ++-- .../tests/ClientPropertyCollectionTests.cs | 2 +- ...ClientPropertyCollectionTestsNewtonsoft.cs | 2 +- iothub/device/tests/NumericHelpersTests.cs | 2 +- iothub/device/tests/TimeoutHelperTests.cs | 2 +- shared/src/PayloadConvention.cs | 2 +- 17 files changed, 51 insertions(+), 57 deletions(-) diff --git a/e2e/test/Helpers/TestDeviceCallbackHandler.cs b/e2e/test/Helpers/TestDeviceCallbackHandler.cs index 6f2025de76..638e317a77 100644 --- a/e2e/test/Helpers/TestDeviceCallbackHandler.cs +++ b/e2e/test/Helpers/TestDeviceCallbackHandler.cs @@ -180,7 +180,7 @@ public async Task SetClientPropertyUpdateCallbackHandlerAsync(string expected string userContext = "myContext"; await _deviceClient - .SubscribeToWritablePropertiesEventAsync( + .SubscribeToWritablePropertyUpdateRequestsAsync( (patch, context) => { _logger.Trace($"{nameof(SetClientPropertyUpdateCallbackHandlerAsync)}: DeviceClient {_testDevice.Id} callback property: WritableProperty: {patch}, {context}"); diff --git a/e2e/test/iothub/properties/PropertiesE2ETests.cs b/e2e/test/iothub/properties/PropertiesE2ETests.cs index 7db552aa57..a38c55d80c 100644 --- a/e2e/test/iothub/properties/PropertiesE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesE2ETests.cs @@ -210,7 +210,7 @@ public static async Task Properties_DeviceSetsPropertyAndGetsItBackAsync(Devi // Validate the updated properties from the device-client ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); - bool isPropertyPresent = clientProperties.TryGetValue(propName, out T propFromCollection); + bool isPropertyPresent = clientProperties.ReportedFromClient.TryGetValue(propName, out T propFromCollection); isPropertyPresent.Should().BeTrue(); propFromCollection.Should().BeEquivalentTo(propValue); @@ -245,7 +245,7 @@ private async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes(C // Set a callback await deviceClient. - SubscribeToWritablePropertiesEventAsync( + SubscribeToWritablePropertyUpdateRequestsAsync( (patch, context) => { Assert.Fail("After having unsubscribed from receiving client property update notifications " + @@ -258,7 +258,7 @@ await deviceClient. // Unsubscribe await deviceClient - .SubscribeToWritablePropertiesEventAsync(null, null) + .SubscribeToWritablePropertyUpdateRequestsAsync(null, null) .ConfigureAwait(false); await RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, propName, propValue) @@ -289,7 +289,7 @@ await Task // Validate the updated properties from the device-client ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); - bool isPropertyPresent = clientProperties.Writable.TryGetValue(propName, out T propValueFromCollection); + bool isPropertyPresent = clientProperties.WritablePropertyRequests.TryGetValue(propName, out T propValueFromCollection); isPropertyPresent.Should().BeTrue(); propValueFromCollection.Should().BeEquivalentTo(propValue); @@ -301,7 +301,7 @@ await Task string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); - await deviceClient.SubscribeToWritablePropertiesEventAsync(null, null).ConfigureAwait(false); + await deviceClient.SubscribeToWritablePropertyUpdateRequestsAsync(null, null).ConfigureAwait(false); await deviceClient.CloseAsync().ConfigureAwait(false); } @@ -319,7 +319,7 @@ private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNe await registryManager.UpdateTwinAsync(testDevice.Id, twinPatch, "*").ConfigureAwait(false); ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); - bool isPropertyPresent = clientProperties.Writable.TryGetValue(propName, out string propFromCollection); + bool isPropertyPresent = clientProperties.WritablePropertyRequests.TryGetValue(propName, out string propFromCollection); isPropertyPresent.Should().BeTrue(); propFromCollection.Should().Be(propValue); diff --git a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs index 3c71d594f6..4016dff7df 100644 --- a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs +++ b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs @@ -129,7 +129,7 @@ static async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testD ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); clientProperties.Should().NotBeNull(); - bool isPropertyPresent = clientProperties.TryGetValue(propName, out string propFromCollection); + bool isPropertyPresent = clientProperties.ReportedFromClient.TryGetValue(propName, out string propFromCollection); isPropertyPresent.Should().BeTrue(); propFromCollection.Should().Be(propValue); } diff --git a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs index 96040512d5..3e3b23be75 100644 --- a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs @@ -212,7 +212,7 @@ public static async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBac // Validate the updated properties from the device-client ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); - bool isPropertyPresent = clientProperties.TryGetValue(ComponentName, propName, out T propFromCollection); + bool isPropertyPresent = clientProperties.ReportedFromClient.TryGetValue(ComponentName, propName, out T propFromCollection); isPropertyPresent.Should().BeTrue(); propFromCollection.Should().BeEquivalentTo(propValue); @@ -252,7 +252,7 @@ private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDevice // Set a callback await deviceClient. - SubscribeToWritablePropertiesEventAsync( + SubscribeToWritablePropertyUpdateRequestsAsync( (patch, context) => { Assert.Fail("After having unsubscribed from receiving client property update notifications " + @@ -265,7 +265,7 @@ await deviceClient. // Unsubscribe await deviceClient - .SubscribeToWritablePropertiesEventAsync(null, null) + .SubscribeToWritablePropertyUpdateRequestsAsync(null, null) .ConfigureAwait(false); await RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, ComponentName, propName, propValue) @@ -296,7 +296,7 @@ await Task // Validate the updated properties from the device-client ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); - bool isPropertyPresent = clientProperties.Writable.TryGetValue(ComponentName, propName, out T propFromCollection); + bool isPropertyPresent = clientProperties.WritablePropertyRequests.TryGetValue(ComponentName, propName, out T propFromCollection); isPropertyPresent.Should().BeTrue(); propFromCollection.Should().BeEquivalentTo(propValue); @@ -308,7 +308,7 @@ await Task string serializedActualPropertyValue = JsonConvert.SerializeObject(actualProp); serializedActualPropertyValue.Should().Be(JsonConvert.SerializeObject(propValue)); - await deviceClient.SubscribeToWritablePropertiesEventAsync(null, null).ConfigureAwait(false); + await deviceClient.SubscribeToWritablePropertyUpdateRequestsAsync(null, null).ConfigureAwait(false); await deviceClient.CloseAsync().ConfigureAwait(false); } @@ -331,7 +331,7 @@ private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDevice await registryManager.UpdateTwinAsync(testDevice.Id, twinPatch, "*").ConfigureAwait(false); ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); - bool isPropertyPresent = clientProperties.Writable.TryGetValue(ComponentName, propName, out string propFromCollection); + bool isPropertyPresent = clientProperties.WritablePropertyRequests.TryGetValue(ComponentName, propName, out string propFromCollection); isPropertyPresent.Should().BeTrue(); propFromCollection.Should().Be(propValue); diff --git a/iothub/device/src/ClientProperties.cs b/iothub/device/src/ClientProperties.cs index 08520d5792..43d708f32d 100644 --- a/iothub/device/src/ClientProperties.cs +++ b/iothub/device/src/ClientProperties.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Devices.Client /// The class is not meant to be constructed by customer code. /// It is intended to be returned fully populated from the client method . /// - public class ClientProperties : ClientPropertyCollection + public class ClientProperties { /// /// Initializes a new instance of . @@ -19,27 +19,36 @@ public class ClientProperties : ClientPropertyCollection /// public ClientProperties() { - Writable = new ClientPropertyCollection(); + WritablePropertyRequests = new ClientPropertyCollection(); + ReportedFromClient = new ClientPropertyCollection(); } /// /// Initializes a new instance of with the specified collections. /// - /// A collection of writable properties returned from IoT Hub. - /// A collection of read-only properties returned from IoT Hub. - internal ClientProperties(ClientPropertyCollection requestedPropertyCollection, ClientPropertyCollection readOnlyPropertyCollection) + /// A collection of writable property requests returned from IoT Hub. + /// A collection of client reported properties returned from IoT Hub. + internal ClientProperties(ClientPropertyCollection writablePropertyRequestCollection, ClientPropertyCollection clientReportedPropertyCollection) { - SetCollection(readOnlyPropertyCollection); - Version = readOnlyPropertyCollection.Version; - Writable = requestedPropertyCollection; + WritablePropertyRequests = writablePropertyRequestCollection; + ReportedFromClient = clientReportedPropertyCollection; } /// - /// The collection of writable properties. + /// The collection of writable property requests received from service. /// /// - /// See the Writable properties documentation for more information. + /// See the Writable properties documentation for more information. /// - public ClientPropertyCollection Writable { get; private set; } + public ClientPropertyCollection WritablePropertyRequests { get; private set; } + + /// + /// The collection of properties reported by the client. + /// + /// + /// Client reported properties can either be Read-only properties + /// or they can be Writable properties. + /// + public ClientPropertyCollection ReportedFromClient { get; private set; } } } diff --git a/iothub/device/src/ClientTwinProperties.cs b/iothub/device/src/ClientTwinProperties.cs index 63b13195f5..dbc27249e4 100644 --- a/iothub/device/src/ClientTwinProperties.cs +++ b/iothub/device/src/ClientTwinProperties.cs @@ -23,10 +23,10 @@ internal ClientTwinProperties() internal ClientProperties ToClientProperties(PayloadConvention payloadConvention) { - ClientPropertyCollection writablePropertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Desired, payloadConvention); - ClientPropertyCollection propertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Reported, payloadConvention); + ClientPropertyCollection writablePropertyRequestCollection = ClientPropertyCollection.FromClientTwinDictionary(Desired, payloadConvention); + ClientPropertyCollection clientReportedPropertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Reported, payloadConvention); - return new ClientProperties(writablePropertyCollection, propertyCollection); + return new ClientProperties(writablePropertyRequestCollection, clientReportedPropertyCollection); } } } diff --git a/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs b/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs index b3e87099a3..c093feca0a 100644 --- a/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs +++ b/iothub/device/src/Common/UrlEncodedDictionarySerializer.cs @@ -345,7 +345,7 @@ private Token CreateToken(TokenType tokenType, int readCount) // '?' is not a valid character for message property names or values, but instead signifies the start of a query string // in the case of an MQTT topic. For this reason, we'll replace the '?' from the property key before adding it into // application properties collection. - // https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-construct + // https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-construct string tokenValue = readCount == 0 ? null : value.Substring(position - readCount, readCount).Replace(QueryStringIdentifier, string.Empty); return new Token(tokenType, tokenValue); diff --git a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs index 3c1e63468d..74619e6d26 100644 --- a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs +++ b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs @@ -67,7 +67,7 @@ public Task UpdateClientPropertiesAsync(ClientPr /// The global call back to handle all writable property updates. /// Generic parameter to be interpreted by the client code. /// A cancellation token to cancel the operation. - public Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken = default) - => InternalClient.SubscribeToWritablePropertiesEventAsync(callback, userContext, cancellationToken); + public Task SubscribeToWritablePropertyUpdateRequestsAsync(Func callback, object userContext, CancellationToken cancellationToken = default) + => InternalClient.SubscribeToWritablePropertyUpdateRequestsAsync(callback, userContext, cancellationToken); } } diff --git a/iothub/device/src/InternalClient.ConventionBasedOperations.cs b/iothub/device/src/InternalClient.ConventionBasedOperations.cs index 4e785e96d0..e4a862bc31 100644 --- a/iothub/device/src/InternalClient.ConventionBasedOperations.cs +++ b/iothub/device/src/InternalClient.ConventionBasedOperations.cs @@ -96,7 +96,7 @@ internal async Task UpdateClientPropertiesAsync( } } - internal Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken) + internal Task SubscribeToWritablePropertyUpdateRequestsAsync(Func callback, object userContext, CancellationToken cancellationToken) { // Subscribe to DesiredPropertyUpdateCallback internally and use the callback received internally to invoke the user supplied Property callback. var desiredPropertyUpdateCallback = new DesiredPropertyUpdateCallback((twinCollection, userContext) => diff --git a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs index 8f8e7eedfb..be6e9adc53 100644 --- a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs +++ b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs @@ -67,7 +67,7 @@ public Task UpdateClientPropertiesAsync(ClientPr /// The global call back to handle all writable property updates. /// Generic parameter to be interpreted by the client code. /// A cancellation token to cancel the operation. - public Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken = default) - => InternalClient.SubscribeToWritablePropertiesEventAsync(callback, userContext, cancellationToken); + public Task SubscribeToWritablePropertyUpdateRequestsAsync(Func callback, object userContext, CancellationToken cancellationToken = default) + => InternalClient.SubscribeToWritablePropertyUpdateRequestsAsync(callback, userContext, cancellationToken); } } diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index a6ab45581a..c9baeb4856 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -193,20 +193,5 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } - - /// - /// Will set the underlying of the payload collection. - /// - /// The collection to get the underlying dictionary from. - protected void SetCollection(PayloadCollection payloadCollection) - { - if (payloadCollection == null) - { - throw new ArgumentNullException(); - } - - Collection = payloadCollection.Collection; - Convention = payloadCollection.Convention; - } } } diff --git a/iothub/device/tests/ClientPropertiesTests.cs b/iothub/device/tests/ClientPropertiesTests.cs index 4dff48991e..243c01a5d3 100644 --- a/iothub/device/tests/ClientPropertiesTests.cs +++ b/iothub/device/tests/ClientPropertiesTests.cs @@ -7,7 +7,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.Azure.Devices.Client.Tests +namespace Microsoft.Azure.Devices.Client.Test { [TestClass] [TestCategory("Unit")] @@ -57,7 +57,7 @@ public void ClientPropertyCollection_CanEnumerateClientProperties() // assert // These are the device reported property values. - foreach (var deviceReportedKeyValuePairs in clientProperties) + foreach (var deviceReportedKeyValuePairs in clientProperties.ReportedFromClient) { if (deviceReportedKeyValuePairs.Key.Equals(StringPropertyName)) { @@ -78,7 +78,7 @@ public void ClientPropertyCollection_CanEnumerateClientProperties() } // These are the property values for which service has requested an update. - foreach (var updateRequestedKeyValuePairs in clientProperties.Writable) + foreach (var updateRequestedKeyValuePairs in clientProperties.WritablePropertyRequests) { if (updateRequestedKeyValuePairs.Key.Equals(DoublePropertyName)) { diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index 125b792bd2..48e3cc6e95 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -7,7 +7,7 @@ using Microsoft.Azure.Devices.Shared; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.Azure.Devices.Client.Tests +namespace Microsoft.Azure.Devices.Client.Test { [TestClass] [TestCategory("Unit")] diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index 7e12e2c9b8..fce64ae95e 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -10,7 +10,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Microsoft.Azure.Devices.Client.Tests +namespace Microsoft.Azure.Devices.Client.Test { [TestClass] [TestCategory("Unit")] diff --git a/iothub/device/tests/NumericHelpersTests.cs b/iothub/device/tests/NumericHelpersTests.cs index d1f4e72684..caa3ad28e8 100644 --- a/iothub/device/tests/NumericHelpersTests.cs +++ b/iothub/device/tests/NumericHelpersTests.cs @@ -7,7 +7,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.Azure.Devices.Client.Tests +namespace Microsoft.Azure.Devices.Client.Test { [TestClass] [TestCategory("Unit")] diff --git a/iothub/device/tests/TimeoutHelperTests.cs b/iothub/device/tests/TimeoutHelperTests.cs index 8a36ddec40..4e95f4898f 100644 --- a/iothub/device/tests/TimeoutHelperTests.cs +++ b/iothub/device/tests/TimeoutHelperTests.cs @@ -8,7 +8,7 @@ using FluentAssertions; using System.Threading.Tasks; -namespace Microsoft.Azure.Devices.Client.Tests +namespace Microsoft.Azure.Devices.Client.Test { /// /// The timeout helper is a way of keeping track of how much time remains against a specified deadline. diff --git a/shared/src/PayloadConvention.cs b/shared/src/PayloadConvention.cs index 99fd943723..468083e9c1 100644 --- a/shared/src/PayloadConvention.cs +++ b/shared/src/PayloadConvention.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Devices.Shared /// The payload convention class. /// /// The payload convention is used to define a specific serializer as well as a specific content encoding. - /// For example, IoT has a convention that is designed + /// For example, IoT has a convention that is designed /// to make it easier to get started with products that use specific conventions by default. public abstract class PayloadConvention { From fef30ff0fd703e6700b6341414f8e1534c99ebea Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 21 Jul 2021 15:52:36 -0700 Subject: [PATCH 42/77] fix(iot-device): Add checks to verify that client property update response has version and requestId (#2115) --- .../device/src/ClientPropertiesUpdateResponse.cs | 5 +++++ iothub/device/src/ClientPropertyCollection.cs | 15 +++++++-------- .../src/Transport/Mqtt/MqttTransportHandler.cs | 15 +++++++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/iothub/device/src/ClientPropertiesUpdateResponse.cs b/iothub/device/src/ClientPropertiesUpdateResponse.cs index a23deb32f3..4db42ab13b 100644 --- a/iothub/device/src/ClientPropertiesUpdateResponse.cs +++ b/iothub/device/src/ClientPropertiesUpdateResponse.cs @@ -20,6 +20,11 @@ public class ClientPropertiesUpdateResponse /// /// The updated version after the property patch has been applied. /// + /// + /// For clients communicating with IoT hub via IoT Edge, since the patch isn't applied immediately an updated version number is not returned. + /// You can call + /// and verify from to check if your patch is successfully applied. + /// public long Version { get; internal set; } } } diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 214889e2ac..2ddb8cf840 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -35,8 +35,7 @@ public void AddRootProperty(string propertyName, object propertyValue) /// /// - /// - /// + /// /// /// Adds the value to the collection. /// @@ -48,17 +47,17 @@ public void AddComponentProperty(string componentName, string propertyName, obje /// /// - /// /// /// Adds the value to the collection. /// /// The component with the properties to add. /// A collection of properties to add. + /// A property name in already exists in the collection. public void AddComponentProperties(string componentName, IDictionary properties) => AddInternal(properties, componentName, true); /// - /// + /// /// /// Adds the collection of root-level property values to the collection. /// @@ -87,19 +86,19 @@ public void AddRootProperties(IDictionary properties) /// /// /// - /// /// The name of the property to add or update. /// The value of the property to add or update. + /// is null. public void AddOrUpdateRootProperty(string propertyName, object propertyValue) => AddInternal(new Dictionary { { propertyName, propertyValue } }, null, true); /// /// /// - /// /// The component with the property to add or update. /// The name of the property to add or update. /// The value of the property to add or update. + /// is null. public void AddOrUpdateComponentProperty(string componentName, string propertyName, object propertyValue) => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); @@ -120,8 +119,7 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionary /// - /// - /// + /// /// /// If the collection has a key that matches this will overwrite the current value. Otherwise it will attempt to add this to the collection. /// @@ -131,6 +129,7 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionary /// /// A collection of properties to add or update. + /// is null. public void AddOrUpdateRootProperties(IDictionary properties) => properties .ToList() diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs index 1ef3e235f3..2fa4cb095c 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs @@ -1020,11 +1020,18 @@ public override async Task SendPropertyPatchAsyn request.MqttTopicName = TwinPatchTopic.FormatInvariant(rid); using Message message = await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); - return new ClientPropertiesUpdateResponse + + var response = new ClientPropertiesUpdateResponse(); + if (message.Properties.TryGetValue(RequestIdKey, out string requestIdRetrieved)) { - RequestId = message.Properties[RequestIdKey], - Version = long.Parse(message.Properties[VersionKey], CultureInfo.InvariantCulture) - }; + response.RequestId = requestIdRetrieved; + } + if (message.Properties.TryGetValue(VersionKey, out string versionRetrievedAsString)) + { + response.Version = long.Parse(versionRetrievedAsString, CultureInfo.InvariantCulture); + } + + return response; } private async Task OpenInternalAsync(CancellationToken cancellationToken) From 84985f74f0194c9ab7cbd7960762352304bd6444 Mon Sep 17 00:00:00 2001 From: Sindhu Nagesh Date: Wed, 21 Jul 2021 16:25:38 -0700 Subject: [PATCH 43/77] refactor(iot-service) Make all clients mockable (#2117) * refactor(job-client): Make job client easy to mock (#1875) * refactor(service-client): Make service client easy to mock (#1878) * refactor(registry-manager): Make registry manager mockable (#1881) * refactor(digital-twin-client): Make the DigitalTwin client mockable. (#1892) --- iothub/service/src/AmqpServiceClient.cs | 419 --- .../src/DigitalTwin/DigitalTwinClient.cs | 122 +- iothub/service/src/HttpRegistryManager.cs | 2235 ---------------- iothub/service/src/JobClient/HttpJobClient.cs | 315 --- iothub/service/src/JobClient/JobClient.cs | 303 ++- iothub/service/src/RegistryManager.cs | 2238 ++++++++++++++++- iothub/service/src/ServiceClient.cs | 415 ++- .../ServiceClientConnectionStringTests.cs | 16 +- .../tests/DeviceAuthenticationTests.cs | 46 +- .../JobClient/DeviceJobParametersTest.cs | 4 +- ...ttpJobClientTests.cs => JobClientTests.cs} | 8 +- iothub/service/tests/RegistryManagerTests.cs | 132 +- iothub/service/tests/ServiceClientTests.cs | 22 +- 13 files changed, 2978 insertions(+), 3297 deletions(-) delete mode 100644 iothub/service/src/AmqpServiceClient.cs delete mode 100644 iothub/service/src/HttpRegistryManager.cs delete mode 100644 iothub/service/src/JobClient/HttpJobClient.cs rename iothub/service/tests/JobClient/{HttpJobClientTests.cs => JobClientTests.cs} (93%) 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 Create( /// 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 async Task> GetDigitalTwi /// 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 Task> UpdateDigital /// 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 async TaskThe 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 Create( } 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 Create( } 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 Create( /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 Create( } 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 Create( } 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// /// 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 @@ protected virtual void Dispose(bool disposing) { } /// /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 @@ protected virtual void Dispose(bool disposing) { } /// 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 Create( 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 Create( 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 @@ Tuple, AmqpServiceClient, PurgeMessageQueueResult> Setup // 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); From 635da5f676517a9fd7e1e68631391b3226a471ad Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 22 Jul 2021 14:15:01 -0700 Subject: [PATCH 44/77] refactor(iot-service): Move direct method request file out of JobClient folder into root --- iothub/service/src/{JobClient => }/CloudToDeviceMethod.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename iothub/service/src/{JobClient => }/CloudToDeviceMethod.cs (100%) diff --git a/iothub/service/src/JobClient/CloudToDeviceMethod.cs b/iothub/service/src/CloudToDeviceMethod.cs similarity index 100% rename from iothub/service/src/JobClient/CloudToDeviceMethod.cs rename to iothub/service/src/CloudToDeviceMethod.cs From e4bbb1e29955babdefd2872950fe2ae268db4fa1 Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Fri, 23 Jul 2021 16:25:21 -0700 Subject: [PATCH 45/77] refactor(prov-amqp, prov-mqtt, prov-http): Fix timeout tests failing occasionally (#2121) --- e2e/test/provisioning/ProvisioningE2ETests.cs | 3 +-- .../transport/amqp/src/ProvisioningTransportHandlerAmqp.cs | 5 +++++ .../transport/http/src/ProvisioningTransportHandlerHttp.cs | 5 +++++ .../transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/e2e/test/provisioning/ProvisioningE2ETests.cs b/e2e/test/provisioning/ProvisioningE2ETests.cs index 0cd498d760..201681fd20 100644 --- a/e2e/test/provisioning/ProvisioningE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningE2ETests.cs @@ -470,9 +470,8 @@ public async Task ProvisioningDeviceClient_ValidRegistrationId_TimeSpanTimeoutRe { await ProvisioningDeviceClient_ValidRegistrationId_Register_Ok(Client.TransportType.Amqp_Tcp_Only, AttestationMechanismType.SymmetricKey, EnrollmentType.Individual, TimeSpan.Zero).ConfigureAwait(false); } - catch (ProvisioningTransportException ex) when (ex.InnerException is SocketException && ((SocketException) ex.InnerException).SocketErrorCode == SocketError.TimedOut) + catch (OperationCanceledException) { - // The expected exception is a bit different in AMQP compared to MQTT/HTTPS return; // expected exception was thrown, so exit the test } diff --git a/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs b/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs index 675412e509..79bb3064ad 100644 --- a/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs +++ b/provisioning/transport/amqp/src/ProvisioningTransportHandlerAmqp.cs @@ -55,6 +55,11 @@ public override async Task RegisterAsync( ProvisioningTransportRegisterMessage message, TimeSpan timeout) { + if (TimeSpan.Zero.Equals(timeout)) + { + throw new OperationCanceledException(); + } + return await RegisterAsync(message, timeout, CancellationToken.None).ConfigureAwait(false); } diff --git a/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs b/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs index cdbd2bec00..6014d906a5 100644 --- a/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs +++ b/provisioning/transport/http/src/ProvisioningTransportHandlerHttp.cs @@ -42,6 +42,11 @@ public override async Task RegisterAsync( ProvisioningTransportRegisterMessage message, TimeSpan timeout) { + if (TimeSpan.Zero.Equals(timeout)) + { + throw new OperationCanceledException(); + } + using var cts = new CancellationTokenSource(timeout); return await RegisterAsync(message, cts.Token).ConfigureAwait(false); } diff --git a/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs b/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs index 99b596d641..51318675cf 100644 --- a/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs +++ b/provisioning/transport/mqtt/src/ProvisioningTransportHandlerMqtt.cs @@ -73,6 +73,11 @@ public override async Task RegisterAsync( ProvisioningTransportRegisterMessage message, TimeSpan timeout) { + if (TimeSpan.Zero.Equals(timeout)) + { + throw new OperationCanceledException(); + } + using var cts = new CancellationTokenSource(timeout); return await RegisterAsync(message, cts.Token).ConfigureAwait(false); } From 4b5e0147f3768761cacaf4913ab6be707425f9da Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 26 Jul 2021 14:34:56 -0700 Subject: [PATCH 46/77] fix(tools): Fix readme for ConsoleEventListener usage (#2123) --- .../AmqpConnectionStatusChange.cs | 0 .../{Helpers => helpers}/ConsoleEventListener.cs | 16 ++++++++++------ e2e/test/{Helpers => helpers}/CustomWebProxy.cs | 0 e2e/test/{Helpers => helpers}/HostNameHelper.cs | 0 .../ImportExportDevicesHelpers.cs | 0 .../{Helpers => helpers}/RetryOperationHelper.cs | 0 .../{Helpers => helpers}/StorageContainer.cs | 0 e2e/test/{Helpers => helpers}/TestDevice.cs | 0 .../TestDeviceCallbackHandler.cs | 0 e2e/test/{Helpers => helpers}/TestModule.cs | 0 .../models/TemperatureControllerTwin.cs | 0 .../digitaltwins/models/ThermostatTwin.cs | 0 .../logging/EventSourceTestLogger.cs | 0 .../logging/LoggedTestMethod.cs | 0 .../logging/LoggingPropertyNames.cs | 0 .../{Helpers => helpers}/logging/MsTestLogger.cs | 0 .../{Helpers => helpers}/logging/TestLogger.cs | 0 .../logging/VerboseTestLogger.cs | 0 .../templates/FaultInjection.cs | 0 .../templates/FaultInjectionPoolingOverAmqp.cs | 0 .../templates/PoolingOverAmqp.cs | 0 21 files changed, 10 insertions(+), 6 deletions(-) rename e2e/test/{Helpers => helpers}/AmqpConnectionStatusChange.cs (100%) rename e2e/test/{Helpers => helpers}/ConsoleEventListener.cs (58%) rename e2e/test/{Helpers => helpers}/CustomWebProxy.cs (100%) rename e2e/test/{Helpers => helpers}/HostNameHelper.cs (100%) rename e2e/test/{Helpers => helpers}/ImportExportDevicesHelpers.cs (100%) rename e2e/test/{Helpers => helpers}/RetryOperationHelper.cs (100%) rename e2e/test/{Helpers => helpers}/StorageContainer.cs (100%) rename e2e/test/{Helpers => helpers}/TestDevice.cs (100%) rename e2e/test/{Helpers => helpers}/TestDeviceCallbackHandler.cs (100%) rename e2e/test/{Helpers => helpers}/TestModule.cs (100%) rename e2e/test/{Helpers => helpers}/digitaltwins/models/TemperatureControllerTwin.cs (100%) rename e2e/test/{Helpers => helpers}/digitaltwins/models/ThermostatTwin.cs (100%) rename e2e/test/{Helpers => helpers}/logging/EventSourceTestLogger.cs (100%) rename e2e/test/{Helpers => helpers}/logging/LoggedTestMethod.cs (100%) rename e2e/test/{Helpers => helpers}/logging/LoggingPropertyNames.cs (100%) rename e2e/test/{Helpers => helpers}/logging/MsTestLogger.cs (100%) rename e2e/test/{Helpers => helpers}/logging/TestLogger.cs (100%) rename e2e/test/{Helpers => helpers}/logging/VerboseTestLogger.cs (100%) rename e2e/test/{Helpers => helpers}/templates/FaultInjection.cs (100%) rename e2e/test/{Helpers => helpers}/templates/FaultInjectionPoolingOverAmqp.cs (100%) rename e2e/test/{Helpers => helpers}/templates/PoolingOverAmqp.cs (100%) diff --git a/e2e/test/Helpers/AmqpConnectionStatusChange.cs b/e2e/test/helpers/AmqpConnectionStatusChange.cs similarity index 100% rename from e2e/test/Helpers/AmqpConnectionStatusChange.cs rename to e2e/test/helpers/AmqpConnectionStatusChange.cs diff --git a/e2e/test/Helpers/ConsoleEventListener.cs b/e2e/test/helpers/ConsoleEventListener.cs similarity index 58% rename from e2e/test/Helpers/ConsoleEventListener.cs rename to e2e/test/helpers/ConsoleEventListener.cs index 8d7a98e74f..d1a3b8d368 100644 --- a/e2e/test/Helpers/ConsoleEventListener.cs +++ b/e2e/test/helpers/ConsoleEventListener.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -10,16 +9,21 @@ namespace System.Diagnostics.Tracing public sealed class ConsoleEventListener : EventListener { // Configure this value to filter all the necessary events when OnEventSourceCreated is called. - // OnEventSourceCreated is triggered as soon as the EventListener is registered and an event source is created. - // So trying to configure this value in the ConsoleEventListener constructor does not work. - // The OnEventSourceCreated can be triggered sooner than the filter is initialized in the ConsoleEventListener constructor. - private static readonly string[] s_eventFilters = new string[] { "DotNetty-Default", "Microsoft-Azure-Devices", "Azure-Core", "Azure-Identity" }; + // The EventListener base class constructor creates an event listener in which all events are disabled by default. + // EventListener constructor also causes the OnEventSourceCreated callback to fire. + // Since our ConsoleEventListener uses the OnEventSourceCreated callback to enable events, the event filter needs to be + // initialized before OnEventSourceCreated is called. For this reason we cannot use ConsoleEventListener constructor + // to initialize the event filter (base class constructors are called before derived class constructors). + // The OnEventSourceCreated will be triggered sooner than the filter is initialized in the ConsoleEventListener constructor. + // As a result we will need to define the event filter list as a static variable. + // Link to EventListener sourcecode: https://github.com/dotnet/runtime/blob/6696065ab0f517f5a9e5f55c559df0010a816dbe/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs#L4009-L4018 + private static readonly string[] s_eventFilter = new string[] { "DotNetty-Default", "Microsoft-Azure-Devices", "Azure-Core", "Azure-Identity" }; private readonly object _lock = new object(); protected override void OnEventSourceCreated(EventSource eventSource) { - if (s_eventFilters.Any(filter => eventSource.Name.StartsWith(filter, StringComparison.OrdinalIgnoreCase))) + if (s_eventFilter.Any(filter => eventSource.Name.StartsWith(filter, StringComparison.OrdinalIgnoreCase))) { base.OnEventSourceCreated(eventSource); EnableEvents( diff --git a/e2e/test/Helpers/CustomWebProxy.cs b/e2e/test/helpers/CustomWebProxy.cs similarity index 100% rename from e2e/test/Helpers/CustomWebProxy.cs rename to e2e/test/helpers/CustomWebProxy.cs diff --git a/e2e/test/Helpers/HostNameHelper.cs b/e2e/test/helpers/HostNameHelper.cs similarity index 100% rename from e2e/test/Helpers/HostNameHelper.cs rename to e2e/test/helpers/HostNameHelper.cs diff --git a/e2e/test/Helpers/ImportExportDevicesHelpers.cs b/e2e/test/helpers/ImportExportDevicesHelpers.cs similarity index 100% rename from e2e/test/Helpers/ImportExportDevicesHelpers.cs rename to e2e/test/helpers/ImportExportDevicesHelpers.cs diff --git a/e2e/test/Helpers/RetryOperationHelper.cs b/e2e/test/helpers/RetryOperationHelper.cs similarity index 100% rename from e2e/test/Helpers/RetryOperationHelper.cs rename to e2e/test/helpers/RetryOperationHelper.cs diff --git a/e2e/test/Helpers/StorageContainer.cs b/e2e/test/helpers/StorageContainer.cs similarity index 100% rename from e2e/test/Helpers/StorageContainer.cs rename to e2e/test/helpers/StorageContainer.cs diff --git a/e2e/test/Helpers/TestDevice.cs b/e2e/test/helpers/TestDevice.cs similarity index 100% rename from e2e/test/Helpers/TestDevice.cs rename to e2e/test/helpers/TestDevice.cs diff --git a/e2e/test/Helpers/TestDeviceCallbackHandler.cs b/e2e/test/helpers/TestDeviceCallbackHandler.cs similarity index 100% rename from e2e/test/Helpers/TestDeviceCallbackHandler.cs rename to e2e/test/helpers/TestDeviceCallbackHandler.cs diff --git a/e2e/test/Helpers/TestModule.cs b/e2e/test/helpers/TestModule.cs similarity index 100% rename from e2e/test/Helpers/TestModule.cs rename to e2e/test/helpers/TestModule.cs diff --git a/e2e/test/Helpers/digitaltwins/models/TemperatureControllerTwin.cs b/e2e/test/helpers/digitaltwins/models/TemperatureControllerTwin.cs similarity index 100% rename from e2e/test/Helpers/digitaltwins/models/TemperatureControllerTwin.cs rename to e2e/test/helpers/digitaltwins/models/TemperatureControllerTwin.cs diff --git a/e2e/test/Helpers/digitaltwins/models/ThermostatTwin.cs b/e2e/test/helpers/digitaltwins/models/ThermostatTwin.cs similarity index 100% rename from e2e/test/Helpers/digitaltwins/models/ThermostatTwin.cs rename to e2e/test/helpers/digitaltwins/models/ThermostatTwin.cs diff --git a/e2e/test/Helpers/logging/EventSourceTestLogger.cs b/e2e/test/helpers/logging/EventSourceTestLogger.cs similarity index 100% rename from e2e/test/Helpers/logging/EventSourceTestLogger.cs rename to e2e/test/helpers/logging/EventSourceTestLogger.cs diff --git a/e2e/test/Helpers/logging/LoggedTestMethod.cs b/e2e/test/helpers/logging/LoggedTestMethod.cs similarity index 100% rename from e2e/test/Helpers/logging/LoggedTestMethod.cs rename to e2e/test/helpers/logging/LoggedTestMethod.cs diff --git a/e2e/test/Helpers/logging/LoggingPropertyNames.cs b/e2e/test/helpers/logging/LoggingPropertyNames.cs similarity index 100% rename from e2e/test/Helpers/logging/LoggingPropertyNames.cs rename to e2e/test/helpers/logging/LoggingPropertyNames.cs diff --git a/e2e/test/Helpers/logging/MsTestLogger.cs b/e2e/test/helpers/logging/MsTestLogger.cs similarity index 100% rename from e2e/test/Helpers/logging/MsTestLogger.cs rename to e2e/test/helpers/logging/MsTestLogger.cs diff --git a/e2e/test/Helpers/logging/TestLogger.cs b/e2e/test/helpers/logging/TestLogger.cs similarity index 100% rename from e2e/test/Helpers/logging/TestLogger.cs rename to e2e/test/helpers/logging/TestLogger.cs diff --git a/e2e/test/Helpers/logging/VerboseTestLogger.cs b/e2e/test/helpers/logging/VerboseTestLogger.cs similarity index 100% rename from e2e/test/Helpers/logging/VerboseTestLogger.cs rename to e2e/test/helpers/logging/VerboseTestLogger.cs diff --git a/e2e/test/Helpers/templates/FaultInjection.cs b/e2e/test/helpers/templates/FaultInjection.cs similarity index 100% rename from e2e/test/Helpers/templates/FaultInjection.cs rename to e2e/test/helpers/templates/FaultInjection.cs diff --git a/e2e/test/Helpers/templates/FaultInjectionPoolingOverAmqp.cs b/e2e/test/helpers/templates/FaultInjectionPoolingOverAmqp.cs similarity index 100% rename from e2e/test/Helpers/templates/FaultInjectionPoolingOverAmqp.cs rename to e2e/test/helpers/templates/FaultInjectionPoolingOverAmqp.cs diff --git a/e2e/test/Helpers/templates/PoolingOverAmqp.cs b/e2e/test/helpers/templates/PoolingOverAmqp.cs similarity index 100% rename from e2e/test/Helpers/templates/PoolingOverAmqp.cs rename to e2e/test/helpers/templates/PoolingOverAmqp.cs From 7ba7f3d0d02c799336bff005649e75451a6187e6 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 26 Jul 2021 15:36:22 -0700 Subject: [PATCH 47/77] fix(tools): Fix readme for ConsoleEventListener usage --- tools/CaptureLogs/readme.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/CaptureLogs/readme.md b/tools/CaptureLogs/readme.md index 794c2f4d24..66fdf0b588 100644 --- a/tools/CaptureLogs/readme.md +++ b/tools/CaptureLogs/readme.md @@ -9,13 +9,18 @@ On Linux and OSX LTTNG and perfcollect can be used to collect traces. For more i ## Console logging Logging can be added to console. Note that this method will substantially slow down execution. - 1. Add `e2e\test\Helpers\ConsoleEventListener.cs` to your project. + 1. Add [`e2e\test\helpers\ConsoleEventListener.cs`](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/e2e/test/helpers/ConsoleEventListener.cs) to your project. 2. Instantiate the listener. Add one or more filters (e.g. `Microsoft-Azure-` or `DotNetty-`): -```C# - private readonly ConsoleEventListener _listener = new ConsoleEventListener("Microsoft-Azure-"); +```csharp + private static readonly ConsoleEventListener _listener = new ConsoleEventListener(); ``` - 3. See the `ConsoleEventListener.cs` file to enable colorized logs within Visual Studio Code. +> NOTE: +> 1. `static` fields are optimized for runtime performance and are initialized prior to their first usage. If `_listener` is the only static field initialized in your class, you'll need to provide a static constructor that initializes them when the class is loaded. +> 2. `ConsoleEventListener.cs` logs the following events by default. If you want to log specific event providers, modify the [event filter](https://github.com/Azure/azure-iot-sdk-csharp/blob/4b5e0147f3768761cacaf4913ab6be707425f9da/e2e/test/helpers/ConsoleEventListener.cs#L20) list to include only your desired event providers. +> ```csharp +> private static readonly string[] s_eventFilter = new string[] { "DotNetty-Default", "Microsoft-Azure-Devices", "Azure-Core", "Azure-Identity" }; +> ``` ## Azure IoT SDK providers From 8ae38913924a7658934f69f48d3e637b350de232 Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Wed, 28 Jul 2021 09:18:55 -0700 Subject: [PATCH 48/77] doc(all): Add documentation for the upcoming service TLS changes for user awareness (#2112) --- readme.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/readme.md b/readme.md index e35f4cb7f0..bd8f6f5609 100644 --- a/readme.md +++ b/readme.md @@ -9,6 +9,22 @@ This repository contains the following: - **Microsoft Azure Provisioning device SDK for C#** to provision devices to Azure IoT Hub with .NET. - **Microsoft Azure Provisioning service SDK for C#** to manage your Provisioning service instance from a back-end .NET application. +## Critical Upcoming Change Notice + +All Azure IoT SDK users are advised to be aware of upcoming TLS certificate changes for Azure IoT Hub and Device Provisioning Service +that will impact the SDK's ability to connect to these services. In October 2022, both services will migrate from the current +[Baltimore CyberTrust CA Root](https://baltimore-cybertrust-root.chain-demos.digicert.com/info/index.html) to the +[DigiCert Global G2 CA root](https://global-root-g2.chain-demos.digicert.com/info/index.html). There will be a +transition period beforehand where your IoT devices must have both the Baltimore and Digicert public certificates +installed in their certificate store in order to prevent connectivity issues. + +**Devices with only the Baltimore public certificate installed will lose the ability to connect to Azure IoT hub and Device Provisioning Service in October 2022.** + +To prepare for this change, make sure your device's certificate store has both of these public certificates installed. + +For a more in depth explanation as to why the IoT services are doing this, please see +[this article](https://techcommunity.microsoft.com/t5/internet-of-things/azure-iot-tls-critical-changes-are-almost-here-and-why-you/ba-p/2393169). + ### Build status Due to security considerations, build logs are not publicly available. From 0f0f9fb63337fedd2f4a29527842d6de41dc3e8d Mon Sep 17 00:00:00 2001 From: Bommas <5359504+scb01@users.noreply.github.com> Date: Mon, 2 Aug 2021 10:05:08 -0700 Subject: [PATCH 49/77] fix(doc): Fix typo is readme --- readme.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index bd8f6f5609..0fb626fd74 100644 --- a/readme.md +++ b/readme.md @@ -210,8 +210,8 @@ Below is a table showing the mapping of the LTS branches to the packages release | [2021-3-18](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18) | lts_2021_03 | Active | 2020-03-18 | 2022-03-18 | 2024-03-17 | | [2020-9-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch1) | lts_2020_08 | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | | [2020-8-19](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19) | lts_2020_08 | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | -| [2020-4-03](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31_patch1) | lts_2020_01 | Depreciated | 2020-01-31 | 2021-01-30 | 2023-01-30 | -| [2020-1-31](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) | lts_2020_01 | Depreciated | 2020-01-31 | 2021-01-30 | 2023-01-30 | +| [2020-4-03](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31_patch1) | lts_2020_01 | Deprecated | 2020-01-31 | 2021-01-30 | 2023-01-30 | +| [2020-1-31](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) | lts_2020_01 | Deprecated | 2020-01-31 | 2021-01-30 | 2023-01-30 | - 1 All scheduled dates are subject to change by the Azure IoT SDK team. @@ -262,4 +262,5 @@ To learn more, review the [privacy statement](https://go.microsoft.com/fwlink/?L [pnp-device-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.DigitalTwin.Client/ [pnp-service-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.DigitalTwin.Service.svg?style=plastic [pnp-service-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.DigitalTwin.Service/ -[pnp-device-dev-guide]: https://docs.microsoft.com/azure/iot-pnp/concepts-developer-guide-device?pivots=programming-language-csharp \ No newline at end of file +[pnp-device-dev-guide]: https://docs.microsoft.com/azure/iot-pnp/concepts-developer-guide-device?pivots=programming-language-csharp + From 1e24ba7547c14886f4551b147c888c330748dfd6 Mon Sep 17 00:00:00 2001 From: Basel Rustum Date: Mon, 2 Aug 2021 17:24:58 -0700 Subject: [PATCH 50/77] Remove barustum from CODEOWNERS (#2126) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1190b2f777..5b905ac5c9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # Track1 .NET Azure IoT Hub and DPS SDKs -* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @barustum @jamdavi \ No newline at end of file +* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @jamdavi From 9ea8b6c1584a5aedcad5b880866c4ede4c27d6b7 Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Thu, 5 Aug 2021 18:28:25 -0700 Subject: [PATCH 51/77] refactor(ado): Update area paths for analyzers pipeline (#2136) --- vsts/vsts.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsts/vsts.yaml b/vsts/vsts.yaml index c6415d2d18..e79cf77d10 100644 --- a/vsts/vsts.yaml +++ b/vsts/vsts.yaml @@ -329,7 +329,7 @@ jobs: notifyAlwaysV2: false instanceUrlForTsaV2: 'MSAZURE' projectNameMSAZURE: 'One' - areaPath: 'One\IoT\Developers and Devices\SDKs\Managed' + areaPath: 'One\IoT\Platform and Devices\IoT Devices\SDKs\Managed' iterationPath: 'One\IoT\Backlog' - task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1 From 2c3cb7371f593d42c21e2bdb64c8764ed077b73e Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 6 Aug 2021 15:25:12 -0700 Subject: [PATCH 52/77] refactor(tools-logs): Update log capture script to be configurable and use powershell (#2141) --- tools/CaptureLogs/iot_startlog.cmd | 2 -- tools/CaptureLogs/iot_startlog.ps1 | 23 +++++++++++++++++++++++ tools/CaptureLogs/iot_stoplog.cmd | 2 -- tools/CaptureLogs/iot_stoplog.ps1 | 17 +++++++++++++++++ tools/CaptureLogs/readme.md | 24 ++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 4 deletions(-) delete mode 100644 tools/CaptureLogs/iot_startlog.cmd create mode 100644 tools/CaptureLogs/iot_startlog.ps1 delete mode 100644 tools/CaptureLogs/iot_stoplog.cmd create mode 100644 tools/CaptureLogs/iot_stoplog.ps1 diff --git a/tools/CaptureLogs/iot_startlog.cmd b/tools/CaptureLogs/iot_startlog.cmd deleted file mode 100644 index 0b79c8a8be..0000000000 --- a/tools/CaptureLogs/iot_startlog.cmd +++ /dev/null @@ -1,2 +0,0 @@ -logman create trace IotTrace -o iot.etl -pf iot_providers.txt -logman start IotTrace diff --git a/tools/CaptureLogs/iot_startlog.ps1 b/tools/CaptureLogs/iot_startlog.ps1 new file mode 100644 index 0000000000..61b921c674 --- /dev/null +++ b/tools/CaptureLogs/iot_startlog.ps1 @@ -0,0 +1,23 @@ +param( + [Parameter(Mandatory)] + [string] $TraceName, + + [Parameter(Mandatory)] + [string] $Output, + + [Parameter(Mandatory)] + [string] $ProviderFile +) + +Function StartLogCapture() +{ + $createTrace = "logman create trace $TraceName -o $Output -pf $ProviderFile" + Write-Host "Invoking: $createTrace." + Invoke-Expression $createTrace + + $startTrace = "logman start $TraceName" + Write-Host "Invoking: $startTrace." + Invoke-Expression $startTrace +} + +StartLogCapture \ No newline at end of file diff --git a/tools/CaptureLogs/iot_stoplog.cmd b/tools/CaptureLogs/iot_stoplog.cmd deleted file mode 100644 index a3e91642cc..0000000000 --- a/tools/CaptureLogs/iot_stoplog.cmd +++ /dev/null @@ -1,2 +0,0 @@ -logman stop IotTrace -logman delete IotTrace diff --git a/tools/CaptureLogs/iot_stoplog.ps1 b/tools/CaptureLogs/iot_stoplog.ps1 new file mode 100644 index 0000000000..eb4db74ebd --- /dev/null +++ b/tools/CaptureLogs/iot_stoplog.ps1 @@ -0,0 +1,17 @@ +param( + [Parameter(Mandatory)] + [string] $TraceName +) + +Function StopLogCapture() +{ + $stopTrace = "logman stop $TraceName" + Write-Host "Invoking: $stopTrace." + Invoke-Expression $stopTrace + + $deleteTrace = "logman delete $TraceName" + Write-Host "Invoking: $deleteTrace." + Invoke-Expression $deleteTrace +} + +StopLogCapture \ No newline at end of file diff --git a/tools/CaptureLogs/readme.md b/tools/CaptureLogs/readme.md index 66fdf0b588..1dc589c93b 100644 --- a/tools/CaptureLogs/readme.md +++ b/tools/CaptureLogs/readme.md @@ -3,6 +3,30 @@ ## Windows On Windows logman or PerfView can be used to collect traces. For more information please see https://github.com/dotnet/runtime/blob/master/docs/workflow/debugging/libraries/windows-instructions.md#traces +We have provided the following convenience scripts for log collection using `logman`. + +1. Launch Powershell with administrator privileges. +2. To start capturing traces, invoke `iot_startlog.ps1`. + 1. Pass in the following required parameters: + 1. `-TraceName` - the name of the event trace data collector. + 2. `-Output` - the output log file that will be created. This should be a `.etl` file. + 3. `-ProviderFile` - The file listing multiple Event Trace providers to enable. The file should be a text file containing one provider per line. + The Azure IoT SDK providers file is present [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/tools/CaptureLogs/iot_providers.txt). The providers list with their corresponding package details are present [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/tools/CaptureLogs#azure-iot-sdk-providers). + + Sample usage: + ```powersehll + .\iot_startlog.ps1 -Output iot.etl -ProviderFile .\iot_providers.txt -TraceName IotTrace + ``` + +3. To stop capturing traces, invoke `iot_stoplog.ps1`. + 1. Pass in the following required parameter: + 1. `-TraceName` - the name of the event trace data collector. Same as the one used while starting trace capture. + + Sample usage: + ```powersehll + .\iot_stoplog.ps1 -TraceName IotTrace + ``` + ## Linux On Linux and OSX LTTNG and perfcollect can be used to collect traces. For more information please see https://github.com/dotnet/runtime/blob/master/docs/project/linux-performance-tracing.md From 39baeb850e816cda031fa6b510f1e7bd64c9b8e3 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Mon, 9 Aug 2021 14:49:32 -0700 Subject: [PATCH 53/77] refactor(iot-device, prov-mqtt): Target new DotNetty version when not net451 (#2129) --- .../src/Microsoft.Azure.Devices.Client.csproj | 6 +- ...icrosoft.Azure.Devices.Client.Tests.csproj | 20 +++---- .../src/Config/IndividualEnrollment.cs | 55 +++---------------- ...Devices.Provisioning.Transport.Mqtt.csproj | 4 +- 4 files changed, 22 insertions(+), 63 deletions(-) diff --git a/iothub/device/src/Microsoft.Azure.Devices.Client.csproj b/iothub/device/src/Microsoft.Azure.Devices.Client.csproj index c16021849f..4d593866d8 100644 --- a/iothub/device/src/Microsoft.Azure.Devices.Client.csproj +++ b/iothub/device/src/Microsoft.Azure.Devices.Client.csproj @@ -68,6 +68,8 @@ + + @@ -75,6 +77,8 @@ + + @@ -82,8 +86,6 @@ - - diff --git a/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj b/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj index 3928ef3cfa..ea152b0a59 100644 --- a/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj +++ b/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj @@ -14,13 +14,17 @@ HsmAuthentication/**;$(DefaultItemExcludes) - - + + + + + + @@ -30,20 +34,12 @@ + + - - - - - - - - - - diff --git a/provisioning/service/src/Config/IndividualEnrollment.cs b/provisioning/service/src/Config/IndividualEnrollment.cs index 6d955e3f70..a45455a655 100644 --- a/provisioning/service/src/Config/IndividualEnrollment.cs +++ b/provisioning/service/src/Config/IndividualEnrollment.cs @@ -156,8 +156,6 @@ internal IndividualEnrollment( string eTag, DeviceCapabilities capabilities) { - /* SRS_INDIVIDUAL_ENROLLMENT_21_003: [The constructor shall throws ProvisioningServiceClientException if one of the - provided parameters in JSON is not valid.] */ if (attestation == null) { throw new ProvisioningServiceClientException("Service respond an individualEnrollment without attestation."); @@ -189,8 +187,7 @@ provided parameters in JSON is not valid.] */ /// The string with the content of this class in a pretty print format. public override string ToString() { - string jsonPrettyPrint = Newtonsoft.Json.JsonConvert.SerializeObject(this, Formatting.Indented); - return jsonPrettyPrint; + return JsonConvert.SerializeObject(this, Formatting.Indented); } /// @@ -201,46 +198,13 @@ public override string ToString() /// /// if the provided string does not fit the registration Id requirements [JsonProperty(PropertyName = "registrationId")] - public string RegistrationId - { - get - { - return _registrationId; - } - - private set - { - _registrationId = value; - } - } - - private string _registrationId; + public string RegistrationId { get; private set; } /// /// Desired IoT Hub device Id (optional). /// [JsonProperty(PropertyName = "deviceId", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string DeviceId - { - get - { - return _deviceId; - } - - set - { - if (value == null) - { - _deviceId = null; - } - else - { - _deviceId = value; - } - } - } - - private string _deviceId; + public string DeviceId { get; set; } /// /// Current registration state. @@ -260,19 +224,16 @@ public string DeviceId [JsonIgnore] public Attestation Attestation { - get - { - return _attestation.GetAttestation(); - } + get => _attestation.GetAttestation(); set { - if (value is X509Attestation) + if (value is X509Attestation attestation) { - if ((((X509Attestation)value ?? throw new ArgumentNullException(nameof(value))).ClientCertificates == null) && - (((X509Attestation)value).CAReferences == null)) + if ((attestation ?? throw new ArgumentNullException(nameof(value))).ClientCertificates == null + && attestation.CAReferences == null) { - throw new ArgumentNullException($"{nameof(value)} do not contains client certificate or CA reference."); + throw new ArgumentNullException(nameof(value), $"Value does not contain client certificate or CA reference."); } } diff --git a/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj b/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj index c252253120..7bd8a388f6 100644 --- a/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj +++ b/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj @@ -97,8 +97,8 @@ - - + + From 41e519b43114fbb0a4ba10ebc3c2ebed6261515a Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 10 Aug 2021 13:24:50 -0700 Subject: [PATCH 54/77] refactor(doc): Add additional comments to log capture readme --- tools/CaptureLogs/readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/CaptureLogs/readme.md b/tools/CaptureLogs/readme.md index 1dc589c93b..6d0efa77ae 100644 --- a/tools/CaptureLogs/readme.md +++ b/tools/CaptureLogs/readme.md @@ -8,12 +8,14 @@ We have provided the following convenience scripts for log collection using `log 1. Launch Powershell with administrator privileges. 2. To start capturing traces, invoke `iot_startlog.ps1`. 1. Pass in the following required parameters: - 1. `-TraceName` - the name of the event trace data collector. + 1. `-TraceName` - the name of the event trace data collector. This can be any name that will be used to identity the collector created. 2. `-Output` - the output log file that will be created. This should be a `.etl` file. 3. `-ProviderFile` - The file listing multiple Event Trace providers to enable. The file should be a text file containing one provider per line. The Azure IoT SDK providers file is present [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/tools/CaptureLogs/iot_providers.txt). The providers list with their corresponding package details are present [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/tools/CaptureLogs#azure-iot-sdk-providers). Sample usage: + + To create an event trace data collector called `IotTrace`, using the file `iot_providers.txt` for the list of event providers to be enabled, putting the results in a file `iot.etl` in the same folder from where the command is invoked, type: ```powersehll .\iot_startlog.ps1 -Output iot.etl -ProviderFile .\iot_providers.txt -TraceName IotTrace ``` From 5349c3f7b849c6ea65ac521510e5838c63fb532b Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 10 Aug 2021 18:03:03 -0700 Subject: [PATCH 55/77] Bump versions for 2021-08-12 release (#2143) --- .../src/Microsoft.Azure.Devices.Client.csproj | 2 +- .../service/src/Microsoft.Azure.Devices.csproj | 2 +- ...ft.Azure.Devices.Provisioning.Client.csproj | 2 +- ...t.Azure.Devices.Provisioning.Service.csproj | 2 +- ....Devices.Provisioning.Transport.Amqp.csproj | 2 +- ....Devices.Provisioning.Transport.Http.csproj | 2 +- ....Devices.Provisioning.Transport.Mqtt.csproj | 2 +- ...re.Devices.Provisioning.Security.Tpm.csproj | 2 +- .../src/Microsoft.Azure.Devices.Shared.csproj | 2 +- versions.csv | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/iothub/device/src/Microsoft.Azure.Devices.Client.csproj b/iothub/device/src/Microsoft.Azure.Devices.Client.csproj index 4d593866d8..59f0aafab8 100644 --- a/iothub/device/src/Microsoft.Azure.Devices.Client.csproj +++ b/iothub/device/src/Microsoft.Azure.Devices.Client.csproj @@ -25,7 +25,7 @@ - 1.37.2 + 1.38.0 Microsoft Azure IoT Device Client SDK True True diff --git a/iothub/service/src/Microsoft.Azure.Devices.csproj b/iothub/service/src/Microsoft.Azure.Devices.csproj index e6b6bea77b..369761f9a5 100644 --- a/iothub/service/src/Microsoft.Azure.Devices.csproj +++ b/iothub/service/src/Microsoft.Azure.Devices.csproj @@ -25,7 +25,7 @@ - 1.34.0 + 1.35.0 Microsoft Azure IoT Service Client SDK True True diff --git a/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj b/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj index 3b3dc759df..417e7cbd39 100644 --- a/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj +++ b/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj @@ -18,7 +18,7 @@ - 1.17.1 + 1.18.0 Microsoft Azure IoT Provisioning Device Client SDK True True diff --git a/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj b/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj index c503049702..c8a073043c 100644 --- a/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj +++ b/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj @@ -19,7 +19,7 @@ - 1.17.1 + 1.17.2 Microsoft Azure IoT Provisioning Service Client SDK True True diff --git a/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj b/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj index 8a0f1c3774..c7782439a8 100644 --- a/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj +++ b/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj @@ -20,7 +20,7 @@ - 1.14.1 + 1.15.0 Microsoft Azure IoT Provisioning Device Client AMQP Transport True True diff --git a/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj b/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj index af4d317ff4..e07f658276 100644 --- a/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj +++ b/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj @@ -20,7 +20,7 @@ - 1.13.1 + 1.14.0 Microsoft Azure IoT Provisioning Device Client HTTP Transport True True diff --git a/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj b/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj index 7bd8a388f6..5f2e0d7c6c 100644 --- a/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj +++ b/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj @@ -20,7 +20,7 @@ - 1.15.1 + 1.16.0 Microsoft Azure IoT Provisioning Device Client MQTT Transport True True diff --git a/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj b/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj index 712461b0db..fc4af673a6 100644 --- a/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj +++ b/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj @@ -28,7 +28,7 @@ - 1.13.1 + 1.13.2 Microsoft Azure IoT Provisioning Device Security TPM Client True True diff --git a/shared/src/Microsoft.Azure.Devices.Shared.csproj b/shared/src/Microsoft.Azure.Devices.Shared.csproj index c62a779b83..72d1b1eaaa 100644 --- a/shared/src/Microsoft.Azure.Devices.Shared.csproj +++ b/shared/src/Microsoft.Azure.Devices.Shared.csproj @@ -18,7 +18,7 @@ - 1.28.1 + 1.29.0 Microsoft Azure IoT Devices Shared True True diff --git a/versions.csv b/versions.csv index a00e605a91..33d5d6b718 100644 --- a/versions.csv +++ b/versions.csv @@ -1,10 +1,10 @@ AssemblyPath, Version -iothub\device\src\Microsoft.Azure.Devices.Client.csproj, 1.37.2 -iothub\service\src\Microsoft.Azure.Devices.csproj, 1.34.0 -shared\src\Microsoft.Azure.Devices.Shared.csproj, 1.28.1 -provisioning\device\src\Microsoft.Azure.Devices.Provisioning.Client.csproj, 1.17.1 -provisioning\transport\amqp\src\Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj, 1.14.1 -provisioning\transport\http\src\Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj, 1.13.1 -provisioning\transport\mqtt\src\Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj, 1.15.1 -security\tpm\src\Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj, 1.13.1 -provisioning\service\src\Microsoft.Azure.Devices.Provisioning.Service.csproj, 1.17.1 +iothub\device\src\Microsoft.Azure.Devices.Client.csproj, 1.38.0 +iothub\service\src\Microsoft.Azure.Devices.csproj, 1.35.0 +shared\src\Microsoft.Azure.Devices.Shared.csproj, 1.29.0 +provisioning\device\src\Microsoft.Azure.Devices.Provisioning.Client.csproj, 1.18.0 +provisioning\transport\amqp\src\Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj, 1.15.0 +provisioning\transport\http\src\Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj, 1.14.0 +provisioning\transport\mqtt\src\Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj, 1.16.0 +security\tpm\src\Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj, 1.13.2 +provisioning\service\src\Microsoft.Azure.Devices.Provisioning.Service.csproj, 1.17.2 From 9782be83e9fe91a8c9e13c8fbfadecd8fbba2a83 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 12 Aug 2021 09:24:47 -0700 Subject: [PATCH 56/77] fix(readme): Update the LTS support dates readme (#2147) --- readme.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 0fb626fd74..1436873e22 100644 --- a/readme.md +++ b/readme.md @@ -206,12 +206,14 @@ Below is a table showing the mapping of the LTS branches to the packages release | Release | Github Branch | LTS Status | LTS Start Date | Maintenance End Date | LTS End Date | | :-------------------------------------------------------------------------------------------: | :-----------: | :--------: | :------------: | :------------------: | :----------: | -| [2021-6-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18_patch1) | lts_2021_03 | Active | 2020-03-18 | 2022-03-18 | 2024-03-17 | -| [2021-3-18](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18) | lts_2021_03 | Active | 2020-03-18 | 2022-03-18 | 2024-03-17 | -| [2020-9-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch1) | lts_2020_08 | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | -| [2020-8-19](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19) | lts_2020_08 | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | -| [2020-4-03](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31_patch1) | lts_2020_01 | Deprecated | 2020-01-31 | 2021-01-30 | 2023-01-30 | -| [2020-1-31](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) | lts_2020_01 | Deprecated | 2020-01-31 | 2021-01-30 | 2023-01-30 | +| [2021-8-12](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18_patch2) | [lts_2021_03](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2021_03) | Active | 2021-08-12 | 2022-03-18 | 2024-03-17 | +| [2021-8-10](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch2) | [lts_2020_08](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2020_08) | Active | 2021-08-10 | 2021-08-19 | 2023-08-19 | +| [2021-6-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18_patch1) | [lts_2021_03](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2021_03) | Active | 2020-06-23 | 2022-03-18 | 2024-03-17 | +| [2021-3-18](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2021-3-18) | [lts_2021_03](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2021_03) | Active | 2020-03-18 | 2022-03-18 | 2024-03-17 | +| [2020-9-23](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19_patch1) | [lts_2020_08](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2020_08) | Active | 2020-09-23 | 2021-08-19 | 2023-08-19 | +| [2020-8-19](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-8-19) | [lts_2020_08](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2020_08) | Active | 2020-08-19 | 2021-08-19 | 2023-08-19 | +| [2020-4-3](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31_patch1) | [lts_2020_01](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2020_01) | Deprecated | 2020-04-03 | 2021-01-30 | 2023-01-30 | +| [2020-1-31](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) | [lts_2020_01](https://github.com/Azure/azure-iot-sdk-csharp/tree/lts_2020_01) | Deprecated | 2020-01-31 | 2021-01-30 | 2023-01-30 | - 1 All scheduled dates are subject to change by the Azure IoT SDK team. From 8f468d062ae9f5cfe6300b2cccad266ab7cf86d8 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Mon, 23 Aug 2021 18:48:41 -0700 Subject: [PATCH 57/77] fix(e2e): get values from create call to avoid timing issues --- .../E2ETestsSetup/e2eTestsSetup.ps1 | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 index 40b45a6647..a23ecb4fbd 100644 --- a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 +++ b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 @@ -71,13 +71,13 @@ Function CleanUp-Certs() { Write-Host "`nCleaning up old certs and files that may cause conflicts." $certsToDelete1 = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object { $_.Issuer.Contains("CN=$subjectPrefix") } - $certsToDelete2 = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object { $_.Issuer.Contains("CN=$groupCertCommonName") } + $certsToDelete2 = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object { $_.Issuer.Contains("CN=$groupCertCommonName") } $certsToDelete3 = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object { $_.Issuer.Contains("CN=$deviceCertCommonName") } $certsToDelete = $certsToDelete1 + $certsToDelete2 + $certsToDelete3 $title = "Cleaning up certs." - $certsToDeleteSubjectNames = $certsToDelete | foreach-object {$_.Subject} + $certsToDeleteSubjectNames = $certsToDelete | foreach-object {$_.Subject} $certsToDeleteSubjectNames = $certsToDeleteSubjectNames -join "`n" $question = "Are you sure you want to delete the following certs?`n`n$certsToDeleteSubjectNames" $choices = '&Yes', '&No' @@ -122,7 +122,7 @@ $hubUploadCertificateName = "rootCA" $iothubUnitsToBeCreated = 1 $managedIdentityName = "$ResourceGroup-user-msi" -# OpenSSL has dropped support for SHA1 signed certificates in ubuntu 20.04, so our test resources will use SHA256 signed certificates instead. +# OpenSSL has dropped support for SHA1 signed certificates in Ubuntu 20.04, so our test resources will use SHA256 signed certificates instead. $certificateHashAlgorithm = "SHA256" ################################################################################################# @@ -166,7 +166,7 @@ if (-not ($keyVaultName -match "^[a-zA-Z][a-zA-Z0-9-]{1,22}[a-zA-Z0-9]$")) } ######################################################################################################## -# Generate self-signed certs and to use in DPS and IoT Hub +# Generate self-signed certs and to use in DPS and IoT hub # New certs will be generated each time you run the script as the script cleans up in the end ######################################################################################################## @@ -266,7 +266,7 @@ Export-Certificate -cert $individualDeviceCert -FilePath $individualDeviceCertPa Export-PFXCertificate -cert $individualDeviceCert -filePath $individualDevicePfxPath -password $certPassword | Out-Null $dpsIndividualX509PfxCertificate = [Convert]::ToBase64String((Get-Content $individualDevicePfxPath -AsByteStream)); -# IoT hub certificate for authemtication. The tests are not setup to use a password for the certificate so create the certificate is created with no password. +# IoT hub certificate for authentication. The tests are not setup to use a password for the certificate so create the certificate is created with no password. $iotHubCert = New-SelfSignedCertificate ` -DnsName "$iotHubCertCommonName" ` -KeySpec Signature ` @@ -275,7 +275,7 @@ $iotHubCert = New-SelfSignedCertificate ` -CertStoreLocation "Cert:\LocalMachine\My" ` -NotAfter (Get-Date).AddYears(2) -# IoT hub certificate signed by intermediate certificate for authemtication. +# IoT hub certificate signed by intermediate certificate for authentication. $iotHubChainDeviceCert = New-SelfSignedCertificate ` -DnsName "$iotHubCertChainDeviceCommonName" ` -KeySpec Signature ` @@ -422,22 +422,22 @@ $iotHubName = az deployment group show -g $ResourceGroup -n $deploymentName --qu ################################################################################################################################################# # Configure an AAD app and create self signed certs and get the bytes to generate more content info. ################################################################################################################################################# -Write-Host "`nCreating App Registration $logAnalyticsAppRegnName" +Write-Host "`nCreating app registration $logAnalyticsAppRegnName" $logAnalyticsAppRegUrl = "http://$logAnalyticsAppRegnName" -az ad sp create-for-rbac -n $logAnalyticsAppRegUrl --role "Reader" --scope $resourceGroupId --output none -$logAnalyticsAppId = az ad app list --display-name $logAnalyticsAppRegnName --query "[?displayName=='$logAnalyticsAppRegnName'].appId" --output tsv -Write-Host "`nApplication $logAnalyticsAppRegnName with Id $logAnalyticsAppId was created successfully." +$logAnalyticsAppId = az ad sp create-for-rbac -n $logAnalyticsAppRegUrl --role "Reader" --scope $resourceGroupId --query "appId" --output tsv +Write-Host "`nCreated application $logAnalyticsAppRegnName with Id $logAnalyticsAppId." ################################################################################################################################################# # Configure an AAD app to perform IoT hub data actions. ################################################################################################################################################# -Write-Host "`nCreating App Registration $iotHubAadTestAppRegName" +Write-Host "`nCreating app registration $iotHubAadTestAppRegName for IoT hub data actions" $iotHubAadTestAppRegUrl = "http://$iotHubAadTestAppRegName" $iotHubDataContributorRoleId = "4fc6c259987e4a07842ec321cc9d413f" $iotHubScope = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Devices/IotHubs/$iotHubName" -$iotHubAadTestAppPassword = az ad sp create-for-rbac -n $iotHubAadTestAppRegUrl --role $iotHubDataContributorRoleId --scope $iotHubScope --query password --output tsv -$iotHubAadTestAppId = az ad app list --display-name $iotHubAadTestAppRegName --query "[?displayName=='$iotHubAadTestAppRegName'].appId" --output tsv -Write-Host "`nApplication $iotHubAadTestAppRegName with Id $iotHubAadTestAppId was created successfully." +$iotHubAadTestAppInfo = az ad sp create-for-rbac -n $iotHubAadTestAppRegUrl --role $iotHubDataContributorRoleId --scope $iotHubScope --query '{appId:appId, password:password}' | ConvertFrom-Json +$iotHubAadTestAppPassword = $iotHubAadTestAppInfo.password +$iotHubAadTestAppId = $iotHubAadTestAppInfo.appId +Write-Host "`nCreated application $iotHubAadTestAppRegName with Id $iotHubAadTestAppId." ################################################################################################################################################# # Add role assignement for User assinged managed identity to be able to perform import and export jobs on the IoT hub. @@ -448,30 +448,30 @@ $msiResourceId = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/p az role assignment create --assignee $msiPrincipalId --role 'Storage Blob Data Contributor' --scope $resourceGroupId --output none ################################################################################################################################## -# Granting the iot hub system idenitty Storage blob contributor access on the resoruce group +# Granting the IoT hub system identity storage blob contributor access on the resoruce group ################################################################################################################################## Write-Host "`nGranting the system identity on the hub $iotHubName Storage Blob Data Contributor permissions on resource group: $ResourceGroup." $systemIdentityPrincipal = az resource list -n $iotHubName --query [0].identity.principalId --out tsv az role assignment create --assignee $systemIdentityPrincipal --role "Storage Blob Data Contributor" --scope $resourceGroupId --output none ################################################################################################################################## -# Uploading ROOT CA certificate to IoTHub and verifying +# Uploading root CA certificate to IoT hub and verifying ################################################################################################################################## $certExits = az iot hub certificate list -g $ResourceGroup --hub-name $iotHubName --query "value[?name=='$hubUploadCertificateName']" --output tsv if ($certExits) { - Write-Host "`nDeleting existing certificate from IoT Hub." + Write-Host "`nDeleting existing certificate from IoT hub." $etag = az iot hub certificate show -g $ResourceGroup --hub-name $iotHubName --name $hubUploadCertificateName --query 'etag' az iot hub certificate delete -g $ResourceGroup --hub-name $iotHubName --name $hubUploadCertificateName --etag $etag } -Write-Host "`nUploading new certificate to IoT Hub." +Write-Host "`nUploading new certificate to IoT hub." az iot hub certificate create -g $ResourceGroup --path $rootCertPath --hub-name $iotHubName --name $hubUploadCertificateName --output none $isVerified = az iot hub certificate show -g $ResourceGroup --hub-name $iotHubName --name $hubUploadCertificateName --query 'properties.isVerified' --output tsv if ($isVerified -eq 'false') { - Write-Host "`nVerifying certificate uploaded to IotHub." + Write-Host "`nVerifying certificate uploaded to IoT hub." $etag = az iot hub certificate show -g $ResourceGroup --hub-name $iotHubName --name $hubUploadCertificateName --query 'etag' $requestedCommonName = az iot hub certificate generate-verification-code -g $ResourceGroup --hub-name $iotHubName --name $hubUploadCertificateName -e $etag --query 'properties.verificationCode' $verificationCertArgs = @{ @@ -495,14 +495,14 @@ $iothubownerSasPolicy = "iothubowner" $iothubownerSasPrimaryKey = az iot hub policy show --hub-name $iotHubName --name $iothubownerSasPolicy --query 'primaryKey' ################################################################################################################################## -# Create device in IoTHub that uses a certificate signed by intermediate certificate +# Create device in IoT hub that uses a certificate signed by intermediate certificate ################################################################################################################################## $iotHubCertChainDevice = az iot hub device-identity list -g $ResourceGroup --hub-name $iotHubName --query "[?deviceId=='$iotHubCertChainDeviceCommonName'].deviceId" --output tsv if (-not $iotHubCertChainDevice) { - Write-Host "`nCreating X509 CA certificate authenticated device $iotHubCertChainDeviceCommonName on IoT Hub." + Write-Host "`nCreating X509 CA certificate authenticated device $iotHubCertChainDeviceCommonName on IoT hub." az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubCertChainDeviceCommonName --am x509_ca } @@ -514,7 +514,7 @@ $iotHubSasBasedDevice = az iot hub device-identity list -g $ResourceGroup --hub- if (-not $iotHubSasBasedDevice) { - Write-Host "`nCreating SAS based device $iotHubSasBasedDeviceId on IoT Hub." + Write-Host "`nCreating SAS-based device $iotHubSasBasedDeviceId on IoT hub." az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubSasBasedDeviceId --ee } $iotHubSasBasedDeviceConnectionString = az iot hub device-identity connection-string show --device-id $iotHubSasBasedDeviceId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv @@ -524,7 +524,7 @@ $iotHubSasBasedModule = az iot hub module-identity list -g $ResourceGroup --hub- if (-not $iotHubSasBasedModule) { - Write-Host "`nCreating SAS based module $iotHubSasBasedModuleId under device $iotHubSasBasedDeviceId on IoT Hub." + Write-Host "`nCreating SAS based module $iotHubSasBasedModuleId under device $iotHubSasBasedDeviceId on IoT hub." az iot hub module-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $iotHubSasBasedDeviceId --module-id $iotHubSasBasedModuleId } $iotHubSasBasedModuleConnectionString = az iot hub module-identity connection-string show --device-id $iotHubSasBasedDeviceId --module-id $iotHubSasBasedModuleId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv @@ -534,7 +534,7 @@ $thermostatSampleDevice = az iot hub device-identity list -g $ResourceGroup --hu if (-not $thermostatSampleDevice) { - Write-Host "`nCreating SAS based device $thermostatSampleDeviceId on IoT Hub." + Write-Host "`nCreating SAS-based device $thermostatSampleDeviceId on IoT hub." az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $thermostatSampleDeviceId --ee } $thermostatSampleDeviceConnectionString = az iot hub device-identity connection-string show --device-id $thermostatSampleDeviceId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv @@ -544,7 +544,7 @@ $temperatureControllerSampleDevice = az iot hub device-identity list -g $Resourc if (-not $temperatureControllerSampleDevice) { - Write-Host "`nCreating SAS based device $temperatureControllerSampleDeviceId on IoT Hub." + Write-Host "`nCreating SAS-based device $temperatureControllerSampleDeviceId on IoT hub." az iot hub device-identity create -g $ResourceGroup --hub-name $iotHubName --device-id $temperatureControllerSampleDeviceId --ee } $temperatureControllerSampleDeviceConnectionString = az iot hub device-identity connection-string show --device-id $temperatureControllerSampleDeviceId --hub-name $iotHubName --resource-group $ResourceGroup --output tsv @@ -566,7 +566,7 @@ az iot dps enrollment create -g $ResourceGroup --dps-name $dpsName --enrollment- $symmetricKeySampleEnrollmentPrimaryKey = az iot dps enrollment show -g $ResourceGroup --dps-name $dpsName --enrollment-id $symmetricKeySampleEnrollmentRegistrationId --show-keys --query 'attestation.symmetricKey.primaryKey' --output tsv ################################################################################################################################## -# Uploading certificate to DPS, verifying and creating enrollment groups +# Uploading certificate to DPS, verifying, and creating enrollment groups ################################################################################################################################## $dpsIdScope = az iot dps show -g $ResourceGroup --name $dpsName --query 'properties.idScope' --output tsv @@ -587,12 +587,12 @@ if ($isVerified -eq 'false') $etag = az iot dps certificate show -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName --query 'etag' $requestedCommonName = az iot dps certificate generate-verification-code -g $ResourceGroup --dps-name $dpsName --certificate-name $uploadCertificateName -e $etag --query 'properties.verificationCode' $verificationCertArgs = @{ - "-DnsName" = $requestedCommonName; - "-CertStoreLocation" = "cert:\LocalMachine\My"; - "-NotAfter" = (get-date).AddYears(2); - "-TextExtension" = @("2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1", "2.5.29.19={text}ca=FALSE&pathlength=0"); - "-HashAlgorithm" = $certificateHashAlgorithm; - "-Signer" = $rootCACert; + "-DnsName" = $requestedCommonName; + "-CertStoreLocation" = "cert:\LocalMachine\My"; + "-NotAfter" = (get-date).AddYears(2); + "-TextExtension" = @("2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1", "2.5.29.19={text}ca=FALSE&pathlength=0"); + "-HashAlgorithm" = $certificateHashAlgorithm; + "-Signer" = $rootCACert; } $verificationCert = New-SelfSignedCertificate @verificationCertArgs Export-Certificate -cert $verificationCert -filePath $verificationCertPath -Type Cert | Out-Null @@ -681,7 +681,7 @@ az keyvault secret set --vault-name $keyVaultName --name "HUB-CHAIN-INTERMEDIATE az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-X509-CHAIN-DEVICE-NAME" --value $iotHubCertChainDeviceCommonName --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-USER-ASSIGNED-MSI-RESOURCE-ID" --value $msiResourceId --output none -# Below Environment variables are only used in Java +# These environment variables are only used in Java az keyvault secret set --vault-name $keyVaultName --name "IOT-DPS-CONNECTION-STRING" --value $dpsConnectionString --output none # DPS Connection string Environment variable for Java az keyvault secret set --vault-name $keyVaultName --name "IOT-DPS-ID-SCOPE" --value $dpsIdScope --output none # DPS ID Scope Environment variable for Java az keyvault secret set --vault-name $keyVaultName --name "FAR-AWAY-IOTHUB-CONNECTION-STRING" --value $farHubConnectionString --output none @@ -696,7 +696,7 @@ az keyvault secret set --vault-name $keyVaultName --name "PROVISIONING-CONNECTIO az keyvault secret set --vault-name $keyVaultName --name "E2E-IKEY" --value $instrumentationKey --output none -# Below environment variables are used by .NET samples +# These environment variables are used by .NET samples az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-DEVICE-CONN-STRING" --value $iotHubSasBasedDeviceConnectionString --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-MODULE-CONN-STRING" --value $iotHubSasBasedModuleConnectionString --output none az keyvault secret set --vault-name $keyVaultName --name "PNP-TC-DEVICE-CONN-STRING" --value $temperatureControllerSampleDeviceConnectionString --output none @@ -708,7 +708,7 @@ az keyvault secret set --vault-name $keyVaultName --name "DPS-SYMMETRIC-KEY-INDI az keyvault secret set --vault-name $keyVaultName --name "DPS-SYMMETRIC-KEY-INDIVIDUAL-ENROLLEMNT-PRIMARY-KEY" --value $symmetricKeySampleEnrollmentPrimaryKey --output none ################################################################################################################################### -# Run docker containers for TPM simulators and Proxy +# Run docker containers for TPM simulators and proxy ################################################################################################################################### if (-not (docker images -q aziotbld/testtpm)) @@ -736,7 +736,7 @@ $file = New-Item -Path $loadScriptDir -Name $loadScriptName -ItemType "file" -Fo Add-Content -Path $file.PSPath -Value "$PSScriptRoot\LoadEnvironmentVariablesFromKeyVault.ps1 -SubscriptionId $SubscriptionId -KeyVaultName $keyVaultName" ############################################################################################################################ -# Configure Environment Variables +# Configure environment variables ############################################################################################################################ Invoke-Expression "$loadScriptDir\$loadScriptName" From 47c26212c7d01abe07ca0cc304be462adf8c75e4 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 25 Aug 2021 11:26:43 -0700 Subject: [PATCH 58/77] refactor(iot-device): Update the internal method names as per functionality --- ...ies.cs => ClientPropertiesAsDictionary.cs} | 12 +- iothub/device/src/ClientPropertyCollection.cs | 14 +- iothub/device/src/IDelegatingHandler.cs | 2 +- ...nternalClient.ConventionBasedOperations.cs | 4 +- .../Transport/Amqp/AmqpTransportHandler.cs | 2 +- .../src/Transport/DefaultDelegatingHandler.cs | 4 +- .../src/Transport/ErrorDelegatingHandler.cs | 4 +- .../src/Transport/HttpTransportHandler.cs | 2 +- .../Transport/Mqtt/MqttTransportHandler.cs | 6 +- .../src/Transport/RetryDelegatingHandler.cs | 4 +- ...ClientPropertyCollectionTestsNewtonsoft.cs | 230 ++++++++++-------- 11 files changed, 155 insertions(+), 129 deletions(-) rename iothub/device/src/{ClientTwinProperties.cs => ClientPropertiesAsDictionary.cs} (71%) diff --git a/iothub/device/src/ClientTwinProperties.cs b/iothub/device/src/ClientPropertiesAsDictionary.cs similarity index 71% rename from iothub/device/src/ClientTwinProperties.cs rename to iothub/device/src/ClientPropertiesAsDictionary.cs index dbc27249e4..e272b557ab 100644 --- a/iothub/device/src/ClientTwinProperties.cs +++ b/iothub/device/src/ClientPropertiesAsDictionary.cs @@ -7,14 +7,8 @@ namespace Microsoft.Azure.Devices.Client { - internal class ClientTwinProperties + internal class ClientPropertiesAsDictionary { - internal ClientTwinProperties() - { - Desired = new Dictionary(); - Reported = new Dictionary(); - } - [JsonProperty(PropertyName = "desired", DefaultValueHandling = DefaultValueHandling.Ignore)] internal IDictionary Desired { get; set; } @@ -23,8 +17,8 @@ internal ClientTwinProperties() internal ClientProperties ToClientProperties(PayloadConvention payloadConvention) { - ClientPropertyCollection writablePropertyRequestCollection = ClientPropertyCollection.FromClientTwinDictionary(Desired, payloadConvention); - ClientPropertyCollection clientReportedPropertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Reported, payloadConvention); + ClientPropertyCollection writablePropertyRequestCollection = ClientPropertyCollection.FromClientPropertiesAsDictionary(Desired, payloadConvention); + ClientPropertyCollection clientReportedPropertyCollection = ClientPropertyCollection.FromClientPropertiesAsDictionary(Reported, payloadConvention); return new ClientProperties(writablePropertyRequestCollection, clientReportedPropertyCollection); } diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 2ddb8cf840..54238bc213 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -256,11 +256,12 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou /// /// Converts a collection to a properties collection. /// - /// This internal class is aware of the implementation of the TwinCollection. + /// This method is used to translate the twin desired properties into writable property update requests. + /// This internal class is aware of the implementation of the TwinCollection. /// The TwinCollection object to convert. /// A convention handler that defines the content encoding and serializer to use for the payload. /// A new instance of the class from an existing using an optional . - internal static ClientPropertyCollection FromTwinCollection(TwinCollection twinCollection, PayloadConvention payloadConvention) + internal static ClientPropertyCollection WritablePropertyUpdateRequestsFromTwinCollection(TwinCollection twinCollection, PayloadConvention payloadConvention) { if (twinCollection == null) { @@ -282,11 +283,12 @@ internal static ClientPropertyCollection FromTwinCollection(TwinCollection twinC return propertyCollectionToReturn; } - internal static ClientPropertyCollection FromClientTwinDictionary(IDictionary clientTwinPropertyDictionary, PayloadConvention payloadConvention) + // This method is used to convert the received twin into client properties (reported + desired). + internal static ClientPropertyCollection FromClientPropertiesAsDictionary(IDictionary clientProperties, PayloadConvention payloadConvention) { - if (clientTwinPropertyDictionary == null) + if (clientProperties == null) { - throw new ArgumentNullException(nameof(clientTwinPropertyDictionary)); + throw new ArgumentNullException(nameof(clientProperties)); } var propertyCollectionToReturn = new ClientPropertyCollection @@ -294,7 +296,7 @@ internal static ClientPropertyCollection FromClientTwinDictionary(IDictionary property in clientTwinPropertyDictionary) + foreach (KeyValuePair property in clientProperties) { // The version information should not be a part of the enumerable ProperyCollection, but rather should be // accessible through its dedicated accessor. diff --git a/iothub/device/src/IDelegatingHandler.cs b/iothub/device/src/IDelegatingHandler.cs index 3729e0ed47..0651bd1bd3 100644 --- a/iothub/device/src/IDelegatingHandler.cs +++ b/iothub/device/src/IDelegatingHandler.cs @@ -69,7 +69,7 @@ internal interface IDelegatingHandler : IContinuationProvider GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken); + Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken); Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken); } diff --git a/iothub/device/src/InternalClient.ConventionBasedOperations.cs b/iothub/device/src/InternalClient.ConventionBasedOperations.cs index e4a862bc31..8b7341565a 100644 --- a/iothub/device/src/InternalClient.ConventionBasedOperations.cs +++ b/iothub/device/src/InternalClient.ConventionBasedOperations.cs @@ -68,7 +68,7 @@ internal async Task GetClientPropertiesAsync(CancellationToken { try { - return await InnerHandler.GetPropertiesAsync(PayloadConvention, cancellationToken).ConfigureAwait(false); + return await InnerHandler.GetClientPropertiesAsync(PayloadConvention, cancellationToken).ConfigureAwait(false); } catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) { @@ -102,7 +102,7 @@ internal Task SubscribeToWritablePropertyUpdateRequestsAsync(Func { // convert a TwinCollection to PropertyCollection - var propertyCollection = ClientPropertyCollection.FromTwinCollection(twinCollection, PayloadConvention); + var propertyCollection = ClientPropertyCollection.WritablePropertyUpdateRequestsFromTwinCollection(twinCollection, PayloadConvention); callback.Invoke(propertyCollection, userContext); return TaskHelpers.CompletedTask; diff --git a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs index 91c32fbace..11fefb6d12 100644 --- a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs +++ b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs @@ -523,7 +523,7 @@ private async Task DisposeMessageAsync(string lockToken, AmqpIotDisposeActions o #region Convention-based operations - public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + "Note that you can still retrieve a client's properties using DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + diff --git a/iothub/device/src/Transport/DefaultDelegatingHandler.cs b/iothub/device/src/Transport/DefaultDelegatingHandler.cs index 19f9e4a4f5..ed9e4cd0cb 100644 --- a/iothub/device/src/Transport/DefaultDelegatingHandler.cs +++ b/iothub/device/src/Transport/DefaultDelegatingHandler.cs @@ -202,10 +202,10 @@ public virtual Task DisableEventReceiveAsync(CancellationToken cancellationToken return InnerHandler?.DisableEventReceiveAsync(cancellationToken) ?? TaskHelpers.CompletedTask; } - public virtual Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public virtual Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { ThrowIfDisposed(); - return InnerHandler?.GetPropertiesAsync(payloadConvention, cancellationToken) ?? Task.FromResult(null); + return InnerHandler?.GetClientPropertiesAsync(payloadConvention, cancellationToken) ?? Task.FromResult(null); } public virtual Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) diff --git a/iothub/device/src/Transport/ErrorDelegatingHandler.cs b/iothub/device/src/Transport/ErrorDelegatingHandler.cs index 058f6ce00e..d5e0be9c77 100644 --- a/iothub/device/src/Transport/ErrorDelegatingHandler.cs +++ b/iothub/device/src/Transport/ErrorDelegatingHandler.cs @@ -145,9 +145,9 @@ public override Task SendMethodResponseAsync(MethodResponseInternal methodRespon return ExecuteWithErrorHandlingAsync(() => base.SendMethodResponseAsync(methodResponse, cancellationToken)); } - public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { - return ExecuteWithErrorHandlingAsync(() => base.GetPropertiesAsync(payloadConvention, cancellationToken)); + return ExecuteWithErrorHandlingAsync(() => base.GetClientPropertiesAsync(payloadConvention, cancellationToken)); } public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) diff --git a/iothub/device/src/Transport/HttpTransportHandler.cs b/iothub/device/src/Transport/HttpTransportHandler.cs index 675e0ad9d7..c93d5e81b8 100644 --- a/iothub/device/src/Transport/HttpTransportHandler.cs +++ b/iothub/device/src/Transport/HttpTransportHandler.cs @@ -403,7 +403,7 @@ public override Task RejectAsync(string lockToken, CancellationToken cancellatio cancellationToken); } - public override Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { throw new NotImplementedException("Property operations are not supported over HTTP. Please use MQTT protocol instead."); } diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs index 2fa4cb095c..b7c23cb247 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs @@ -977,7 +977,7 @@ public override async Task SendTwinPatchAsync(TwinCollection reportedProperties, await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); } - public override async Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override async Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); EnsureValidState(); @@ -993,8 +993,8 @@ public override async Task GetPropertiesAsync(PayloadConventio try { - ClientTwinProperties twinProperties = JsonConvert.DeserializeObject(body); - var properties = twinProperties.ToClientProperties(payloadConvention); + ClientPropertiesAsDictionary clientPropertiesAsDictionary = JsonConvert.DeserializeObject(body); + var properties = clientPropertiesAsDictionary.ToClientProperties(payloadConvention); return properties; } catch (JsonReaderException ex) diff --git a/iothub/device/src/Transport/RetryDelegatingHandler.cs b/iothub/device/src/Transport/RetryDelegatingHandler.cs index 35ead90d7b..de36ab6284 100644 --- a/iothub/device/src/Transport/RetryDelegatingHandler.cs +++ b/iothub/device/src/Transport/RetryDelegatingHandler.cs @@ -606,7 +606,7 @@ await _internalRetryPolicy } } - public override async Task GetPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override async Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) { try { @@ -617,7 +617,7 @@ public override async Task GetPropertiesAsync(PayloadConventio async () => { await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); - return await base.GetPropertiesAsync(payloadConvention, cancellationToken).ConfigureAwait(false); + return await base.GetClientPropertiesAsync(payloadConvention, cancellationToken).ConfigureAwait(false); }, cancellationToken) .ConfigureAwait(false); diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index fce64ae95e..3b171a2b43 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -16,20 +16,32 @@ namespace Microsoft.Azure.Devices.Client.Test [TestCategory("Unit")] // These tests test the deserialization of the service response to a ClientPropertyCollection. // This flow is convention aware and uses NewtonSoft.Json for deserialization. - // For the purpose of these tests we will create an instance of a Twin class to simulate the service response. public class ClientPropertyCollectionTestsNewtonsoft { - internal const string BoolPropertyName = "boolPropertyName"; - internal const string DoublePropertyName = "doublePropertyName"; - internal const string FloatPropertyName = "floatPropertyName"; - internal const string IntPropertyName = "intPropertyName"; - internal const string ShortPropertyName = "shortPropertyName"; - internal const string StringPropertyName = "stringPropertyName"; - internal const string ObjectPropertyName = "objectPropertyName"; - internal const string ArrayPropertyName = "arrayPropertyName"; - internal const string MapPropertyName = "mapPropertyName"; - internal const string DateTimePropertyName = "dateTimePropertyName"; + internal const string RootBoolPropertyName = "rootBoolPropertyName"; + internal const string RootDoublePropertyName = "rootDoublePropertyName"; + internal const string RootFloatPropertyName = "rootFloatPropertyName"; + internal const string RootIntPropertyName = "rootIntPropertyName"; + internal const string RootShortPropertyName = "rootShortPropertyName"; + internal const string RootStringPropertyName = "rootStringPropertyName"; + internal const string RootObjectPropertyName = "rootObjectPropertyName"; + internal const string RootArrayPropertyName = "rootArrayPropertyName"; + internal const string RootMapPropertyName = "rootMapPropertyName"; + internal const string RootDateTimePropertyName = "rootDateTimePropertyName"; + internal const string RootWritablePropertyName = "rootWritablePropertyName"; + internal const string ComponentName = "testableComponent"; + internal const string ComponentBoolPropertyName = "componentBoolPropertyName"; + internal const string ComponentDoublePropertyName = "componentDoublePropertyName"; + internal const string ComponentFloatPropertyName = "componentFloatPropertyName"; + internal const string ComponentIntPropertyName = "componentIntPropertyName"; + internal const string ComponentShortPropertyName = "componentShortPropertyName"; + internal const string ComponentStringPropertyName = "componentStringPropertyName"; + internal const string ComponentObjectPropertyName = "componentObjectPropertyName"; + internal const string ComponentArrayPropertyName = "componentArrayPropertyName"; + internal const string ComponentMapPropertyName = "componentMapPropertyName"; + internal const string ComponentDateTimePropertyName = "componentDateTimePropertyName"; + internal const string ComponentWritablePropertyName = "componentWritablePropertyName"; private const bool BoolPropertyValue = false; private const double DoublePropertyValue = 1.001; @@ -58,8 +70,15 @@ public class ClientPropertyCollectionTestsNewtonsoft { "key3", s_objectPropertyValue } }; - // Create an object that represents all of the properties as top-level properties. - private static readonly RootLevelProperties s_rootLevelProperties = new RootLevelProperties + // Create a writable property response with the expected values. + private static readonly IWritablePropertyResponse s_writablePropertyResponse = new NewtonsoftJsonWritablePropertyResponse( + propertyValue: StringPropertyValue, + ackCode: CommonClientResponseCodes.OK, + ackVersion: 2, + ackDescription: "testableWritablePropertyDescription"); + + // Create an object that represents a client instance having top-level and component-level properties. + private static readonly TestProperties s_testClientProperties = new TestProperties { BooleanProperty = BoolPropertyValue, DoubleProperty = DoublePropertyValue, @@ -70,14 +89,9 @@ public class ClientPropertyCollectionTestsNewtonsoft ObjectProperty = s_objectPropertyValue, ArrayProperty = s_arrayPropertyValue, MapProperty = s_mapPropertyValue, - DateTimeProperty = s_dateTimePropertyValue - }; - - // Create an object that represents all of the properties as component-level properties. - // This adds the "__t": "c" component identifier as a part of "ComponentProperties" class declaration. - private static readonly ComponentLevelProperties s_componentLevelProperties = new ComponentLevelProperties - { - Properties = new ComponentProperties + DateTimeProperty = s_dateTimePropertyValue, + WritablePropertyResponse = s_writablePropertyResponse, + ComponentProperties = new ComponentProperties { BooleanProperty = BoolPropertyValue, DoubleProperty = DoublePropertyValue, @@ -88,77 +102,56 @@ public class ClientPropertyCollectionTestsNewtonsoft ObjectProperty = s_objectPropertyValue, ArrayProperty = s_arrayPropertyValue, MapProperty = s_mapPropertyValue, - DateTimeProperty = s_dateTimePropertyValue - }, + DateTimeProperty = s_dateTimePropertyValue, + WritablePropertyResponse = s_writablePropertyResponse, + } }; - // Create a writable property response with the expected values. - private static readonly IWritablePropertyResponse s_writablePropertyResponse = new NewtonsoftJsonWritablePropertyResponse( - propertyValue: StringPropertyValue, - ackCode: CommonClientResponseCodes.OK, - ackVersion: 2, - ackDescription: "testableWritablePropertyDescription"); - - // Create a JObject instance that represents a writable property response sent for a top-level property. - private static readonly JObject s_writablePropertyResponseJObject = new JObject( - new JProperty(StringPropertyName, JObject.FromObject(s_writablePropertyResponse))); - - // Create a JObject instance that represents a writable property response sent for a component-level property. - // This adds the "__t": "c" component identifier to the constructed JObject. - private static readonly JObject s_writablePropertyResponseWithComponentJObject = new JObject( - new JProperty(ComponentName, new JObject( - new JProperty(ConventionBasedConstants.ComponentIdentifierKey, ConventionBasedConstants.ComponentIdentifierValue), - new JProperty(StringPropertyName, JObject.FromObject(s_writablePropertyResponse))))); + private static string getClientPropertiesStringResponse = JsonConvert.SerializeObject(new Dictionary { { "reported", s_testClientProperties } }); - // The above constructed json objects are used for initializing a twin response. - // This is because we are using a Twin instance to simulate the service response. - - private static TwinCollection collectionToRoundTrip = new TwinCollection(JsonConvert.SerializeObject(s_rootLevelProperties)); - private static TwinCollection collectionWithComponentToRoundTrip = new TwinCollection(JsonConvert.SerializeObject(s_componentLevelProperties)); - private static TwinCollection collectionWritablePropertyToRoundTrip = new TwinCollection(s_writablePropertyResponseJObject, null); - private static TwinCollection collectionWritablePropertyWithComponentToRoundTrip = new TwinCollection(s_writablePropertyResponseWithComponentJObject, null); + private static ClientPropertiesAsDictionary clientPropertiesAsDictionary = JsonConvert.DeserializeObject(getClientPropertiesStringResponse); [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetValue() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act, assert - clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); + clientProperties.TryGetValue(RootStringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); - clientProperties.TryGetValue(BoolPropertyName, out bool boolOutValue); + clientProperties.TryGetValue(RootBoolPropertyName, out bool boolOutValue); boolOutValue.Should().Be(BoolPropertyValue); - clientProperties.TryGetValue(DoublePropertyName, out double doubleOutValue); + clientProperties.TryGetValue(RootDoublePropertyName, out double doubleOutValue); doubleOutValue.Should().Be(DoublePropertyValue); - clientProperties.TryGetValue(FloatPropertyName, out float floatOutValue); + clientProperties.TryGetValue(RootFloatPropertyName, out float floatOutValue); floatOutValue.Should().Be(FloatPropertyValue); - clientProperties.TryGetValue(IntPropertyName, out int intOutValue); + clientProperties.TryGetValue(RootIntPropertyName, out int intOutValue); intOutValue.Should().Be(IntPropertyValue); - clientProperties.TryGetValue(ShortPropertyName, out short shortOutValue); + clientProperties.TryGetValue(RootShortPropertyName, out short shortOutValue); shortOutValue.Should().Be(ShortPropertyValue); - clientProperties.TryGetValue(ObjectPropertyName, out CustomClientProperty objectOutValue); + clientProperties.TryGetValue(RootObjectPropertyName, out CustomClientProperty objectOutValue); objectOutValue.Id.Should().Be(s_objectPropertyValue.Id); objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); // The two lists won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the list is deserialized to a JObject. - clientProperties.TryGetValue(ArrayPropertyName, out List arrayOutValue); + clientProperties.TryGetValue(RootArrayPropertyName, out List arrayOutValue); arrayOutValue.Should().HaveSameCount(s_arrayPropertyValue); // The two dictionaries won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the dictionary is deserialized to a JObject. - clientProperties.TryGetValue(MapPropertyName, out Dictionary mapOutValue); + clientProperties.TryGetValue(RootMapPropertyName, out Dictionary mapOutValue); mapOutValue.Should().HaveSameCount(s_mapPropertyValue); - clientProperties.TryGetValue(DateTimePropertyName, out DateTimeOffset dateTimeOutValue); + clientProperties.TryGetValue(RootDateTimePropertyName, out DateTimeOffset dateTimeOutValue); dateTimeOutValue.Should().Be(s_dateTimePropertyValue); } @@ -166,43 +159,43 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValue() public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act, assert - clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); + clientProperties.TryGetValue(ComponentName, ComponentStringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); - clientProperties.TryGetValue(ComponentName, BoolPropertyName, out bool boolOutValue); + clientProperties.TryGetValue(ComponentName, ComponentBoolPropertyName, out bool boolOutValue); boolOutValue.Should().Be(BoolPropertyValue); - clientProperties.TryGetValue(ComponentName, DoublePropertyName, out double doubleOutValue); + clientProperties.TryGetValue(ComponentName, ComponentDoublePropertyName, out double doubleOutValue); doubleOutValue.Should().Be(DoublePropertyValue); - clientProperties.TryGetValue(ComponentName, FloatPropertyName, out float floatOutValue); + clientProperties.TryGetValue(ComponentName, ComponentFloatPropertyName, out float floatOutValue); floatOutValue.Should().Be(FloatPropertyValue); - clientProperties.TryGetValue(ComponentName, IntPropertyName, out int intOutValue); + clientProperties.TryGetValue(ComponentName, ComponentIntPropertyName, out int intOutValue); intOutValue.Should().Be(IntPropertyValue); - clientProperties.TryGetValue(ComponentName, ShortPropertyName, out short shortOutValue); + clientProperties.TryGetValue(ComponentName, ComponentShortPropertyName, out short shortOutValue); shortOutValue.Should().Be(ShortPropertyValue); - clientProperties.TryGetValue(ComponentName, ObjectPropertyName, out CustomClientProperty objectOutValue); + clientProperties.TryGetValue(ComponentName, ComponentObjectPropertyName, out CustomClientProperty objectOutValue); objectOutValue.Id.Should().Be(s_objectPropertyValue.Id); objectOutValue.Name.Should().Be(s_objectPropertyValue.Name); // The two lists won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the list is deserialized to a JObject. - clientProperties.TryGetValue(ComponentName, ArrayPropertyName, out List arrayOutValue); + clientProperties.TryGetValue(ComponentName, ComponentArrayPropertyName, out List arrayOutValue); arrayOutValue.Should().HaveSameCount(s_arrayPropertyValue); // The two dictionaries won't be exactly equal since TryGetValue doesn't implement nested deserialization // => the complex object inside the dictionary is deserialized to a JObject. - clientProperties.TryGetValue(ComponentName, MapPropertyName, out Dictionary mapOutValue); + clientProperties.TryGetValue(ComponentName, ComponentMapPropertyName, out Dictionary mapOutValue); mapOutValue.Should().HaveSameCount(s_mapPropertyValue); - clientProperties.TryGetValue(ComponentName, DateTimePropertyName, out DateTimeOffset dateTimeOutValue); + clientProperties.TryGetValue(ComponentName, ComponentDateTimePropertyName, out DateTimeOffset dateTimeOutValue); dateTimeOutValue.Should().Be(s_dateTimePropertyValue); } @@ -210,10 +203,10 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGetBack() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act - clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + clientProperties.TryGetValue(RootWritablePropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); // assert outValue.Value.Should().Be(StringPropertyValue); @@ -226,10 +219,10 @@ public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGe public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithComponentAndGetBack() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act - clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + clientProperties.TryGetValue(ComponentName, ComponentWritablePropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); // assert outValue.Value.Should().Be(StringPropertyValue); @@ -242,10 +235,10 @@ public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithCompone public void ClientPropertyCollectionNewtonsoft_CanGetComponentIdentifier() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act - clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); + clientProperties.TryGetValue(ComponentName, ComponentStringPropertyName, out string outValue); clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); // assert @@ -257,7 +250,7 @@ public void ClientPropertyCollectionNewtonsoft_CanGetComponentIdentifier() public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfValueNotFound() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act bool isValueRetrieved = clientProperties.TryGetValue("thisPropertyDoesNotExist", out int outIntValue); @@ -271,7 +264,7 @@ public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfVal public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfValueNotFound() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, "thisPropertyDoesNotExist", out int outIntValue); @@ -284,9 +277,13 @@ public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldRet [TestMethod] public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfValueCouldNotBeDeserialized() { - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + // arrange + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(RootStringPropertyName, out int outIntValue); - bool isValueRetrieved = clientProperties.TryGetValue(StringPropertyName, out int outIntValue); + // assert isValueRetrieved.Should().BeFalse(); outIntValue.Should().Be(default); } @@ -295,10 +292,10 @@ public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfVal public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfValueCouldNotBeDeserialized() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); // act - bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, StringPropertyName, out int outIntValue); + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, ComponentStringPropertyName, out int outIntValue); // assert isValueRetrieved.Should().BeFalse(); @@ -309,8 +306,8 @@ public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldRet public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfNotAComponent() { // arrange - var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); - string incorrectlyMappedComponentName = MapPropertyName; + var clientProperties = ClientPropertyCollection.FromClientPropertiesAsDictionary(clientPropertiesAsDictionary.Reported, DefaultPayloadConvention.Instance); + string incorrectlyMappedComponentName = ComponentMapPropertyName; string incorrectlyMappedComponentPropertyName = "key1"; // act @@ -322,48 +319,81 @@ public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldRet } } - internal class RootLevelProperties + internal class TestProperties { - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.BoolPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootBoolPropertyName)] public bool BooleanProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.DoublePropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootDoublePropertyName)] public double DoubleProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.FloatPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootFloatPropertyName)] public float FloatProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.IntPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootIntPropertyName)] public int IntProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ShortPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootShortPropertyName)] public short ShortProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.StringPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootStringPropertyName)] public string StringProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ObjectPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootObjectPropertyName)] public object ObjectProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ArrayPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootArrayPropertyName)] public IList ArrayProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.MapPropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootMapPropertyName)] public IDictionary MapProperty { get; set; } - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.DateTimePropertyName)] + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootDateTimePropertyName)] public DateTimeOffset DateTimeProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.RootWritablePropertyName)] + public IWritablePropertyResponse WritablePropertyResponse { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentName)] + public ComponentProperties ComponentProperties { get; set; } } - internal class ComponentProperties : RootLevelProperties + internal class ComponentProperties { [JsonProperty(ConventionBasedConstants.ComponentIdentifierKey)] public string ComponentIdentifier { get; } = ConventionBasedConstants.ComponentIdentifierValue; - } - internal class ComponentLevelProperties - { - [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentName)] - public ComponentProperties Properties { get; set; } + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentBoolPropertyName)] + public bool BooleanProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentDoublePropertyName)] + public double DoubleProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentFloatPropertyName)] + public float FloatProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentIntPropertyName)] + public int IntProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentShortPropertyName)] + public short ShortProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentStringPropertyName)] + public string StringProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentObjectPropertyName)] + public object ObjectProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentArrayPropertyName)] + public IList ArrayProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentMapPropertyName)] + public IDictionary MapProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentDateTimePropertyName)] + public DateTimeOffset DateTimeProperty { get; set; } + + [JsonProperty(ClientPropertyCollectionTestsNewtonsoft.ComponentWritablePropertyName)] + public IWritablePropertyResponse WritablePropertyResponse { get; set; } } } From 6ec2eb83260165d17035a98f2bba1f1970280c4c Mon Sep 17 00:00:00 2001 From: Sindhu Nagesh Date: Thu, 26 Aug 2021 09:57:56 -0700 Subject: [PATCH 59/77] refactor(e2e): Remove unnecessary value in keyvault from setup file (#2153) --- e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 index a23ecb4fbd..f43fd14e52 100644 --- a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 +++ b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 @@ -701,7 +701,6 @@ az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-DEVICE-CONN-STR az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-MODULE-CONN-STRING" --value $iotHubSasBasedModuleConnectionString --output none az keyvault secret set --vault-name $keyVaultName --name "PNP-TC-DEVICE-CONN-STRING" --value $temperatureControllerSampleDeviceConnectionString --output none az keyvault secret set --vault-name $keyVaultName --name "PNP-THERMOSTAT-DEVICE-CONN-STRING" --value $thermostatSampleDeviceConnectionString --output none -az keyvault secret set --vault-name $keyVaultName --name "PATH-TO-DEVICE-PREFIX-FOR-DELETION-FILE" --value "csharp_devices_list.csv" --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-SAS-KEY" --value $iothubownerSasPrimaryKey --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-SAS-KEY-NAME" --value $iothubownerSasPolicy --output none az keyvault secret set --vault-name $keyVaultName --name "DPS-SYMMETRIC-KEY-INDIVIDUAL-ENROLLMENT-REGISTRATION-ID" --value $symmetricKeySampleEnrollmentRegistrationId --output none From 8772c04e89c3dfc633143f45af9f2ade2f28fdbd Mon Sep 17 00:00:00 2001 From: Sindhu Nagesh Date: Thu, 26 Aug 2021 11:40:17 -0700 Subject: [PATCH 60/77] fix(e2e): Fix e2e setup script to choose the correct dps endpoint based on region of the resource (#2134) --- e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 index f43fd14e52..f9eed88cd5 100644 --- a/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 +++ b/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1 @@ -659,7 +659,14 @@ az keyvault secret set --vault-name $keyVaultName --name "FAR-AWAY-IOTHUB-HOSTNA az keyvault secret set --vault-name $keyVaultName --name "DPS-IDSCOPE" --value $dpsIdScope --output none az keyvault secret set --vault-name $keyVaultName --name "PROVISIONING-CONNECTION-STRING" --value $dpsConnectionString --output none az keyvault secret set --vault-name $keyVaultName --name "CUSTOM-ALLOCATION-POLICY-WEBHOOK" --value $customAllocationPolicyWebhook --output none -az keyvault secret set --vault-name $keyVaultName --name "DPS-GLOBALDEVICEENDPOINT" --value "global.azure-devices-provisioning.net" --output none + +$dpsEndpoint = "global.azure-devices-provisioning.net" +if ($Region.EndsWith('euap', 'CurrentCultureIgnoreCase')) +{ + $dpsEndpoint = "global-canary.azure-devices-provisioning.net" +} +az keyvault secret set --vault-name $keyVaultName --name "DPS-GLOBALDEVICEENDPOINT" --value $dpsEndpoint --output none + az keyvault secret set --vault-name $keyVaultName --name "DPS-X509-PFX-CERTIFICATE-PASSWORD" --value $dpsX509PfxCertificatePassword --output none az keyvault secret set --vault-name $keyVaultName --name "IOTHUB-X509-PFX-CERTIFICATE" --value $iothubX509PfxCertificate --output none az keyvault secret set --vault-name $keyVaultName --name "DPS-INDIVIDUALX509-PFX-CERTIFICATE" --value $dpsIndividualX509PfxCertificate --output none From 4a9eecb3f7437146ee60c76e7bd57dca88832a80 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Fri, 27 Aug 2021 09:14:40 -0700 Subject: [PATCH 61/77] fix(readme): Update readme to specify preview strategy --- readme.md | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/readme.md b/readme.md index 1436873e22..aa4ef97d86 100644 --- a/readme.md +++ b/readme.md @@ -36,28 +36,30 @@ Due to security considerations, build logs are not publicly available. ### Recommended NuGet packages -| Package Name | Release Version | Pre-release Version | -| --- | --- | --- | -| Microsoft.Azure.Devices.Client | [![NuGet][iothub-device-release]][iothub-device-nuget] | [![NuGet][iothub-device-prerelease]][iothub-device-nuget] | -| Microsoft.Azure.Devices | [![NuGet][iothub-service-release]][iothub-service-nuget] | [![NuGet][iothub-service-prerelease]][iothub-service-nuget] | -| Microsoft.Azure.Devices.Shared | [![NuGet][iothub-shared-release]][iothub-shared-nuget] | [![NuGet][iothub-shared-prerelease]][iothub-shared-nuget] | -| Microsoft.Azure.Devices.Provisioning.Client | [![NuGet][dps-device-release]][dps-device-nuget] | [![NuGet][dps-device-prerelease]][dps-device-nuget] | -| Microsoft.Azure.Devices.Provisioning.Transport.Amqp | [![NuGet][dps-device-amqp-release]][dps-device-amqp-nuget]| [![NuGet][dps-device-amqp-prerelease]][dps-device-amqp-nuget] | -| Microsoft.Azure.Devices.Provisioning.Transport.Http | [![NuGet][dps-device-http-release]][dps-device-http-nuget]| [![NuGet][dps-device-http-prerelease]][dps-device-http-nuget] | -| Microsoft.Azure.Devices.Provisioning.Transport.Mqtt | [![NuGet][dps-device-mqtt-release]][dps-device-mqtt-nuget]| [![NuGet][dps-device-mqtt-prerelease]][dps-device-mqtt-nuget] | -| Microsoft.Azure.Devices.Provisioning.Service | [![NuGet][dps-service-release]][dps-service-nuget] | [![NuGet][dps-service-prerelease]][dps-service-nuget] | -| Microsoft.Azure.Devices.Provisioning.Security.Tpm | [![NuGet][dps-tpm-release]][dps-tpm-nuget] | [![NuGet][dps-tpm-prerelease]][dps-tpm-nuget] | -| Microsoft.Azure.Devices.DigitalTwin.Client | N/A | [![NuGet][pnp-device-prerelease]][pnp-device-nuget] | -| Microsoft.Azure.Devices.DigitalTwin.Service | N/A | [![NuGet][pnp-service-prerelease]][pnp-service-nuget] | +| Package Name | Release Version | +| --- | --- | +| Microsoft.Azure.Devices.Client | [![NuGet][iothub-device-release]][iothub-device-nuget] | +| Microsoft.Azure.Devices | [![NuGet][iothub-service-release]][iothub-service-nuget] | +| Microsoft.Azure.Devices.Shared | [![NuGet][iothub-shared-release]][iothub-shared-nuget] | +| Microsoft.Azure.Devices.Provisioning.Client | [![NuGet][dps-device-release]][dps-device-nuget] | +| Microsoft.Azure.Devices.Provisioning.Transport.Amqp | [![NuGet][dps-device-amqp-release]][dps-device-amqp-nuget]| +| Microsoft.Azure.Devices.Provisioning.Transport.Http | [![NuGet][dps-device-http-release]][dps-device-http-nuget]| +| Microsoft.Azure.Devices.Provisioning.Transport.Mqtt | [![NuGet][dps-device-mqtt-release]][dps-device-mqtt-nuget]| +| Microsoft.Azure.Devices.Provisioning.Service | [![NuGet][dps-service-release]][dps-service-nuget] | +| Microsoft.Azure.Devices.Provisioning.Security.Tpm | [![NuGet][dps-tpm-release]][dps-tpm-nuget] | +| Microsoft.Azure.Devices.DigitalTwin.Client | N/A | +| Microsoft.Azure.Devices.DigitalTwin.Service | N/A | > Note: -> Device streaming feature is not being included in our newer preview releases as there is no active development going on in the service. For more details on the feature, see [here](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-device-streams-overview). It is not recommended to take dependency on preview nugets for production applications as breaking changes can be introduced in preview nugets. +> 1. In addition to stable builds we also release pre-release builds that contain preview features. You can find details about the preview features released by looking at the [release notes](https://github.com/Azure/azure-iot-sdk-csharp/releases). It is not recommended to take dependency on preview NuGets for production applications as breaking changes can be introduced in preview packages. +> 2. Device streaming feature is not being included in our newer preview releases as there is no active development going on in the service. For more details on the feature, see [here](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-device-streams-overview). > -> The feature has not been included in any preview release after [2020-10-14](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/preview_2020-10-14). However, the feature is still available under previews/deviceStreaming branch. +> This feature has not been included in any preview release after [2020-10-14](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/preview_2020-10-14). However, the feature is still available under [previews/deviceStreaming branch](https://github.com/Azure/azure-iot-sdk-csharp/tree/previews/deviceStreaming). > -> The latest preview nuget versions that contain the feature are: -Microsoft.Azure.Devices.Client - 1.32.0-preview-001 -Microsoft.Azure.Devices - 1.28.0-preview-001 +> The latest preview NuGet versions that contain the device streaming feature are: + Microsoft.Azure.Devices.Client - 1.32.0-preview-001 + Microsoft.Azure.Devices - 1.28.0-preview-001 +> 3. Stable and preview NuGet versions are not interdependent; eg. for NuGet packages versioned 1.25.0 (stable release) and 1.25.0-preview-001 (preview release), there is no guarantee that v1.25.0 contains the feature(s) previewed in v1.25.0-preview-001. For a list of updates shipped with each NuGet package, please refer to the [release notes](https://github.com/Azure/azure-iot-sdk-csharp/releases). The API reference documentation for .NET SDK is [here][dotnet-api-reference]. @@ -234,35 +236,24 @@ To learn more, review the [privacy statement](https://go.microsoft.com/fwlink/?L [azure-iot-sdks]: https://github.com/azure/azure-iot-sdks [dotnet-api-reference]: https://docs.microsoft.com/dotnet/api/overview/azure/iot/client?view=azure-dotnet [iothub-device-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Client.svg?style=plastic -[iothub-device-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Client.svg?style=plastic [iothub-device-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Client/ [iothub-service-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.svg?style=plastic -[iothub-service-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.svg?style=plastic [iothub-service-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices/ [iothub-shared-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Shared.svg?style=plastic -[iothub-shared-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Shared.svg?style=plastic [iothub-shared-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Shared/ [dps-device-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Provisioning.Client.svg?style=plastic -[dps-device-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Provisioning.Client.svg?style=plastic [dps-device-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Provisioning.Client/ [dps-device-amqp-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.svg?style=plastic -[dps-device-amqp-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.svg?style=plastic [dps-device-amqp-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Provisioning.Transport.Amqp/ [dps-device-http-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Provisioning.Transport.Http.svg?style=plastic -[dps-device-http-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Provisioning.Transport.Http.svg?style=plastic [dps-device-http-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Provisioning.Transport.Http/ [dps-device-mqtt-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.svg?style=plastic -[dps-device-mqtt-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.svg?style=plastic [dps-device-mqtt-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt/ [dps-service-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Provisioning.Service.svg?style=plastic -[dps-service-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Provisioning.Service.svg?style=plastic [dps-service-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Provisioning.Service/ [dps-tpm-release]: https://img.shields.io/nuget/v/Microsoft.Azure.Devices.Provisioning.Security.Tpm.svg?style=plastic -[dps-tpm-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.Provisioning.Security.Tpm.svg?style=plastic [dps-tpm-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.Provisioning.Security.Tpm/ -[pnp-device-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.DigitalTwin.Client.svg?style=plastic [pnp-device-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.DigitalTwin.Client/ -[pnp-service-prerelease]: https://img.shields.io/nuget/vpre/Microsoft.Azure.Devices.DigitalTwin.Service.svg?style=plastic [pnp-service-nuget]: https://www.nuget.org/packages/Microsoft.Azure.Devices.DigitalTwin.Service/ [pnp-device-dev-guide]: https://docs.microsoft.com/azure/iot-pnp/concepts-developer-guide-device?pivots=programming-language-csharp From bd7b2e41b8ed6125b602c16850a7c03e2e4c5ada Mon Sep 17 00:00:00 2001 From: Sindhu Nagesh Date: Mon, 30 Aug 2021 10:28:49 -0700 Subject: [PATCH 62/77] Removing vinagesh from codebase owners list --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5b905ac5c9..407bbf8e1f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # Track1 .NET Azure IoT Hub and DPS SDKs -* @drwill-ms @timtay-microsoft @abhipsaMisra @vinagesh @azabbasi @jamdavi +* @drwill-ms @timtay-microsoft @abhipsaMisra @azabbasi @jamdavi From fc774ff6cf8343d78ac42ae6cf0e1d48de9c73aa Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Tue, 31 Aug 2021 12:58:56 -0700 Subject: [PATCH 63/77] feat(iot-device): Add convenience method for acknowledging writable property update requests --- configure_tls_protocol_version_and_ciphers.md | 6 +- .../iothub/properties/PropertiesE2ETests.cs | 112 ++++++++++++++++ .../PropertiesWithComponentsE2ETests.cs | 118 +++++++++++++++++ .../devdoc/Convention-based operations.md | 15 ++- .../src/ClientPropertiesUpdateResponse.cs | 4 + iothub/device/src/ClientPropertyCollection.cs | 121 ++++++++++++++++-- iothub/device/src/DeviceClient.cs | 26 ++-- iothub/device/src/ModuleClient.cs | 8 +- iothub/device/src/NumericHelpers.cs | 45 ------- iothub/device/src/ObjectConversionHelpers.cs | 82 ++++++++++++ iothub/device/src/PayloadCollection.cs | 69 +++++++++- .../Transport/Mqtt/MqttTransportSettings.cs | 4 +- iothub/device/src/WritableClientProperty.cs | 51 ++++++++ iothub/device/tests/DeviceClientTests.cs | 6 +- ...persTests.cs => ObjectCastHelpersTests.cs} | 4 +- .../service/src/Common/Data/AccessRights.cs | 6 +- .../src/DigitalTwin/DigitalTwinClient.cs | 4 +- .../Serialization/UpdateOperationsUtility.cs | 10 +- iothub/service/src/FeedbackStatusCode.cs | 10 +- iothub/service/src/JobClient/JobClient.cs | 2 +- iothub/service/src/JobProperties.cs | 2 +- iothub/service/src/ManagedIdentity.cs | 4 +- .../service/src/MessageSystemPropertyNames.cs | 2 +- iothub/service/src/RegistryManager.cs | 2 +- iothub/service/src/ServiceClient.cs | 6 +- .../device/src/PlugAndPlay/PnpConvention.cs | 2 +- readme.md | 2 +- shared/src/NewtonsoftJsonPayloadSerializer.cs | 19 ++- shared/src/PayloadSerializer.cs | 3 +- supported_platforms.md | 2 +- 30 files changed, 624 insertions(+), 123 deletions(-) delete mode 100644 iothub/device/src/NumericHelpers.cs create mode 100644 iothub/device/src/ObjectConversionHelpers.cs create mode 100644 iothub/device/src/WritableClientProperty.cs rename iothub/device/tests/{NumericHelpersTests.cs => ObjectCastHelpersTests.cs} (88%) diff --git a/configure_tls_protocol_version_and_ciphers.md b/configure_tls_protocol_version_and_ciphers.md index e19fee5a76..007f862d20 100644 --- a/configure_tls_protocol_version_and_ciphers.md +++ b/configure_tls_protocol_version_and_ciphers.md @@ -24,9 +24,9 @@ For more information, check out this article about [best practices with .NET and Also follow the instructions on how to [enable and disable ciphers]. [.NET Framework 4.5.1 is no longer supported]: https://devblogs.microsoft.com/dotnet/support-ending-for-the-net-framework-4-4-5-and-4-5-1/ -[TLS registry settings]: https://docs.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings -[best practices with .NEt and TLS]: https://docs.microsoft.com/en-us/dotnet/framework/network-programming/tls -[enable and disable ciphers]: https://support.microsoft.com/en-us/help/245030/how-to-restrict-the-use-of-certain-cryptographic-algorithms-and-protoc +[TLS registry settings]: https://docs.microsoft.com/windows-server/security/tls/tls-registry-settings +[best practices with .NEt and TLS]: https://docs.microsoft.com/dotnet/framework/network-programming/tls +[enable and disable ciphers]: https://support.microsoft.com/help/245030/how-to-restrict-the-use-of-certain-cryptographic-algorithms-and-protoc ### Linux Instructions diff --git a/e2e/test/iothub/properties/PropertiesE2ETests.cs b/e2e/test/iothub/properties/PropertiesE2ETests.cs index a38c55d80c..13d907c604 100644 --- a/e2e/test/iothub/properties/PropertiesE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesE2ETests.cs @@ -118,6 +118,42 @@ await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( .ConfigureAwait(false); } + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_Mqtt() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_Tcp_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_MqttWs() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_WebSocket_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_Mqtt() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_Tcp_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_MqttWs() + { + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_WebSocket_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + [LoggedTestMethod] public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_Mqtt() { @@ -305,6 +341,82 @@ await Task await deviceClient.CloseAsync().ConfigureAwait(false); } + private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync(Client.TransportType transport, T propValue) + { + using var cts = new CancellationTokenSource(s_maxWaitTimeForCallback); + string propName = Guid.NewGuid().ToString(); + + Logger.Trace($"{nameof(Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + var writablePropertyCallbackSemaphore = new SemaphoreSlim(0, 1); + await deviceClient + .SubscribeToWritablePropertyUpdateRequestsAsync( + async (writableProperties, userContext) => + { + try + { + bool isPropertyPresent = writableProperties.TryGetValue(propName, out T propertyFromCollection); + + isPropertyPresent.Should().BeTrue(); + propertyFromCollection.Should().BeEquivalentTo(propValue); + userContext.Should().BeNull(); + + var writablePropertyAcks = new ClientPropertyCollection(); + foreach (KeyValuePair writableProperty in writableProperties) + { + if (writableProperty.Value is WritableClientProperty writableClientProperty) + { + writablePropertyAcks.Add(writableProperty.Key, writableClientProperty.AcknowledgeWith(CommonClientResponseCodes.OK)); + } + } + + await deviceClient.UpdateClientPropertiesAsync(writablePropertyAcks).ConfigureAwait(false); + } + finally + { + writablePropertyCallbackSemaphore.Release(); + } + }, + null, + cts.Token) + .ConfigureAwait(false); + + using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); + + await Task + .WhenAll( + RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, propName, propValue), + writablePropertyCallbackSemaphore.WaitAsync(cts.Token)) + .ConfigureAwait(false); + + // Validate the updated properties from the device-client + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + + // Validate that the writable property update request was received + bool isWritablePropertyRequestPresent = clientProperties.WritablePropertyRequests.TryGetValue(propName, out T writablePropertyRequest); + isWritablePropertyRequestPresent.Should().BeTrue(); + writablePropertyRequest.Should().BeEquivalentTo(propValue); + + // Validate that the writable property update request was acknowledged + + bool isWritablePropertyAckPresent = clientProperties.ReportedFromClient.TryGetValue(propName, out IWritablePropertyResponse writablePropertyAck); + isWritablePropertyAckPresent.Should().BeTrue(); + // TryGetValue doesn't have nested deserialization, so we'll have to deserialize the retrieved value + deviceClient.PayloadConvention.PayloadSerializer.ConvertFromObject(writablePropertyAck.Value).Should().BeEquivalentTo(propValue); + + bool isWritablePropertyAckPresentSpecific = clientProperties.ReportedFromClient.TryGetValue(propName, out NewtonsoftJsonWritablePropertyResponse writablePropertyAckNewtonSoft); + isWritablePropertyAckPresentSpecific.Should().BeTrue(); + // TryGetValue doesn't have nested deserialization, so we'll have to deserialize the retrieved value + deviceClient.PayloadConvention.PayloadSerializer.ConvertFromObject(writablePropertyAckNewtonSoft.Value).Should().BeEquivalentTo(propValue); + + bool isWritablePropertyAckPresentAsValue = clientProperties.ReportedFromClient.TryGetValue(propName, out T writablePropertyAckValue); + isWritablePropertyAckPresentAsValue.Should().BeTrue(); + writablePropertyAckValue.Should().BeEquivalentTo(propValue); + } + private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync(Client.TransportType transport) { string propName = Guid.NewGuid().ToString(); diff --git a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs index 3e3b23be75..1ce6d0fb7e 100644 --- a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs @@ -184,6 +184,42 @@ await PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync( .ConfigureAwait(false); } + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_Mqtt() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_Tcp_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_MqttWs() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_WebSocket_Only, + Guid.NewGuid().ToString()) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_Mqtt() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_Tcp_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + + [LoggedTestMethod] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_MqttWs() + { + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( + Client.TransportType.Mqtt_WebSocket_Only, + s_mapOfPropertyValues) + .ConfigureAwait(false); + } + private async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); @@ -312,6 +348,88 @@ await Task await deviceClient.CloseAsync().ConfigureAwait(false); } + private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync(Client.TransportType transport, T propValue) + { + using var cts = new CancellationTokenSource(s_maxWaitTimeForCallback); + string propName = Guid.NewGuid().ToString(); + + Logger.Trace($"{nameof(PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync)}: name={propName}, value={propValue}"); + + TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + + var writablePropertyCallbackSemaphore = new SemaphoreSlim(0, 1); + await deviceClient + .SubscribeToWritablePropertyUpdateRequestsAsync( + async (writableProperties, userContext) => + { + try + { + bool isPropertyPresent = writableProperties.TryGetValue(ComponentName, propName, out T propertyFromCollection); + + isPropertyPresent.Should().BeTrue(); + propertyFromCollection.Should().BeEquivalentTo(propValue); + userContext.Should().BeNull(); + + var writablePropertyAcks = new ClientPropertyCollection(); + foreach (KeyValuePair writableProperty in writableProperties) + { + if (writableProperty.Value is IDictionary componentProperties) + { + foreach (KeyValuePair componentProperty in componentProperties) + { + if (componentProperty.Value is WritableClientProperty writableClientProperty) + { + writablePropertyAcks.AddComponentProperty(writableProperty.Key, componentProperty.Key, writableClientProperty.AcknowledgeWith(CommonClientResponseCodes.OK)); + } + } + } + } + + await deviceClient.UpdateClientPropertiesAsync(writablePropertyAcks).ConfigureAwait(false); + } + finally + { + writablePropertyCallbackSemaphore.Release(); + } + }, + null, + cts.Token) + .ConfigureAwait(false); + + using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); + + await Task + .WhenAll( + RegistryManagerUpdateWritablePropertyAsync(testDevice.Id, ComponentName, propName, propValue), + writablePropertyCallbackSemaphore.WaitAsync(cts.Token)) + .ConfigureAwait(false); + + // Validate the updated properties from the device-client + ClientProperties clientProperties = await deviceClient.GetClientPropertiesAsync().ConfigureAwait(false); + + // Validate that the writable property update request was received + bool isWritablePropertyRequestPresent = clientProperties.WritablePropertyRequests.TryGetValue(ComponentName, propName, out T writablePropertyRequest); + isWritablePropertyRequestPresent.Should().BeTrue(); + writablePropertyRequest.Should().BeEquivalentTo(propValue); + + // Validate that the writable property update request was acknowledged + + bool isWritablePropertyAckPresent = clientProperties.ReportedFromClient.TryGetValue(ComponentName, propName, out IWritablePropertyResponse writablePropertyAck); + isWritablePropertyAckPresent.Should().BeTrue(); + // TryGetValue doesn't have nested deserialization, so we'll have to deserialize the retrieved value + deviceClient.PayloadConvention.PayloadSerializer.ConvertFromObject(writablePropertyAck.Value).Should().BeEquivalentTo(propValue); + + bool isWritablePropertyAckPresentSpecific = clientProperties.ReportedFromClient.TryGetValue(ComponentName, propName, out NewtonsoftJsonWritablePropertyResponse writablePropertyAckNewtonSoft); + isWritablePropertyAckPresentSpecific.Should().BeTrue(); + // TryGetValue doesn't have nested deserialization, so we'll have to deserialize the retrieved value + deviceClient.PayloadConvention.PayloadSerializer.ConvertFromObject(writablePropertyAckNewtonSoft.Value).Should().BeEquivalentTo(propValue); + + bool isWritablePropertyAckPresentAsValue = clientProperties.ReportedFromClient.TryGetValue(ComponentName, propName, out T writablePropertyAckValue); + isWritablePropertyAckPresentAsValue.Should().BeTrue(); + writablePropertyAckValue.Should().BeEquivalentTo(propValue); + } + private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync(Client.TransportType transport) { string propName = Guid.NewGuid().ToString(); diff --git a/iothub/device/devdoc/Convention-based operations.md b/iothub/device/devdoc/Convention-based operations.md index 25437da767..49216ecc89 100644 --- a/iothub/device/devdoc/Convention-based operations.md +++ b/iothub/device/devdoc/Convention-based operations.md @@ -79,7 +79,6 @@ public abstract class PayloadCollection : IEnumerable, IEnumerable> GetEnumerator(); public virtual byte[] GetPayloadObjectBytes(); public virtual string GetSerializedString(); - protected void SetCollection(PayloadCollection payloadCollection); IEnumerator System.Collections.IEnumerable.GetEnumerator(); public bool TryGetValue(string key, out T value); } @@ -127,15 +126,16 @@ public Task UpdateClientPropertiesAsync(ClientPr /// The global call back to handle all writable property updates. /// Generic parameter to be interpreted by the client code. /// A cancellation token to cancel the operation. -public Task SubscribeToWritablePropertiesEventAsync(Func callback, object userContext, CancellationToken cancellationToken = default); +public Task SubscribeToWritablePropertyUpdateRequestsAsync(Func callback, object userContext, CancellationToken cancellationToken = default); ``` #### All related types ```csharp -public class ClientProperties : ClientPropertyCollection { +public class ClientProperties { public ClientProperties(); - public ClientPropertyCollection Writable { get; private set; } + public ClientPropertyCollection ReportedFromClient { get; private set; } + public ClientPropertyCollection WritablePropertyRequests { get; private set; } } public class ClientPropertyCollection : PayloadCollection { @@ -153,6 +153,12 @@ public class ClientPropertyCollection : PayloadCollection { public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue); } +public class WritableClientProperty { + public object Value { get; internal set; } + public long Version { get; internal set; } + public IWritablePropertyResponse AcknowledgeWith(int statusCode, string description = null); +} + public interface IWritablePropertyResponse { int AckCode { get; set; } string AckDescription { get; set; } @@ -169,7 +175,6 @@ public sealed class NewtonsoftJsonWritablePropertyResponse : IWritablePropertyRe } public class ClientPropertiesUpdateResponse { - public ClientPropertiesUpdateResponse(); public string RequestId { get; internal set; } public long Version { get; internal set; } } diff --git a/iothub/device/src/ClientPropertiesUpdateResponse.cs b/iothub/device/src/ClientPropertiesUpdateResponse.cs index 4db42ab13b..b204260794 100644 --- a/iothub/device/src/ClientPropertiesUpdateResponse.cs +++ b/iothub/device/src/ClientPropertiesUpdateResponse.cs @@ -8,6 +8,10 @@ namespace Microsoft.Azure.Devices.Client /// public class ClientPropertiesUpdateResponse { + internal ClientPropertiesUpdateResponse() + { + } + /// /// The request Id that is associated with the operation. /// diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 54238bc213..17729ad2c0 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Azure.Devices.Shared; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.Azure.Devices.Client { @@ -188,12 +190,23 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou return false; } + // While retrieving the property value from the collection: + // 1. A property collection constructed by the client application - can be retrieved using dictionary indexer. + // 2. Client property received through writable property update callbacks - stored internally as a WritableClientProperty. + // 3. Client property returned through GetClientProperties: + // a. Client reported properties sent by the client application in response to writable property update requests - stored as a JSON object + // and needs to be converted to an IWritablePropertyResponse implementation using the payload serializer. + // b. Client reported properties sent by the client application - stored as a JSON object + // and needs to be converted to the expected type using the payload serializer. + // c. Writable property update request received - stored as a JSON object + // and needs to be converted to the expected type using the payload serializer. + if (Contains(componentName, propertyName)) { object componentProperties = Collection[componentName]; // If the ClientPropertyCollection was constructed by the user application (eg. for updating the client properties) - // then the componentProperties are retrieved as a dictionary. + // or was returned by the application as a writable property update request then the componentProperties are retrieved as a dictionary. // The required property value can be fetched from the dictionary directly. if (componentProperties is IDictionary nestedDictionary) { @@ -211,21 +224,32 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou return true; } + // Case 1: // If the object is of type T or can be cast to type T, go ahead and return it. if (dictionaryElement is T valueRef - || NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) + || ObjectConversionHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) { propertyValue = valueRef; return true; } + + // Case 2: + // Check if the retrieved value is a writable property update request + if (dictionaryElement is WritableClientProperty writableClientProperty) + { + object writableClientPropertyValue = writableClientProperty.Value; + + // If the object is of type T or can be cast or converted to type T, go ahead and return it. + if (ObjectConversionHelpers.TryCastOrConvert(writableClientPropertyValue, Convention, out propertyValue)) + { + return true; + } + } } } } else { - // If the ClientPropertyCollection was constructed by the SDK (eg. when retrieving the client properties) - // then the componentProperties are retrieved as the json object that is defined in the PayloadConvention. - // The required property value then needs to be deserialized accordingly. try { // First verify that the retrieved dictionary contains the component identifier { "__t": "c" }. @@ -235,10 +259,46 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou .TryGetNestedObjectValue(componentProperties, ConventionBasedConstants.ComponentIdentifierKey, out string componentIdentifierValue) && componentIdentifierValue == ConventionBasedConstants.ComponentIdentifierValue) { + Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out object retrievedPropertyValue); + + try + { + // Case 3a: + // Check if the retrieved value is a writable property update acknowledgment + var newtonsoftWritablePropertyResponse = Convention.PayloadSerializer.ConvertFromObject(retrievedPropertyValue); + + if (typeof(IWritablePropertyResponse).IsAssignableFrom(typeof(T))) + { + // If T is IWritablePropertyResponse the property value should be of type IWritablePropertyResponse as defined in the PayloadSerializer. + // We'll convert the json object to NewtonsoftJsonWritablePropertyResponse and then convert it to the appropriate IWritablePropertyResponse object. + propertyValue = (T)Convention.PayloadSerializer.CreateWritablePropertyResponse( + newtonsoftWritablePropertyResponse.Value, + newtonsoftWritablePropertyResponse.AckCode, + newtonsoftWritablePropertyResponse.AckVersion, + newtonsoftWritablePropertyResponse.AckDescription); + return true; + } + + object writablePropertyValue = newtonsoftWritablePropertyResponse.Value; + + // If the object is of type T or can be cast or converted to type T, go ahead and return it. + if (ObjectConversionHelpers.TryCastOrConvert(writablePropertyValue, Convention, out propertyValue)) + { + return true; + } + } + catch + { + // In case of an exception ignore it and continue. + } + + // Case 3b, 3c: // Since the value cannot be cast to directly, we need to try to convert it using the serializer. // If it can be successfully converted, go ahead and return it. - Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); - return true; + if (Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue)) + { + return true; + } } } catch @@ -275,7 +335,50 @@ internal static ClientPropertyCollection WritablePropertyUpdateRequestsFromTwinC foreach (KeyValuePair property in twinCollection) { - propertyCollectionToReturn.Add(property.Key, payloadConvention.PayloadSerializer.DeserializeToType(Newtonsoft.Json.JsonConvert.SerializeObject(property.Value))); + object propertyValueAsObject = property.Value; + string propertyValueAsString = DefaultPayloadConvention.Instance.PayloadSerializer.SerializeToString(propertyValueAsObject); + + // Check if the property value is for a root property or a component property. + // A component property be a JObject and will have the "__t": "c" identifiers. + bool isComponentProperty = propertyValueAsObject is JObject + && payloadConvention.PayloadSerializer.TryGetNestedObjectValue(propertyValueAsString, ConventionBasedConstants.ComponentIdentifierKey, out string _); + + if (isComponentProperty) + { + // If this is a component property then the collection is a JObject with each individual property as a writable property update request. + var propertyValueAsJObject = (JObject)propertyValueAsObject; + var collectionDictionary = new Dictionary(propertyValueAsJObject.Count); + + foreach (KeyValuePair componentProperty in propertyValueAsJObject) + { + object individualPropertyValue; + if (componentProperty.Key == ConventionBasedConstants.ComponentIdentifierKey) + { + individualPropertyValue = componentProperty.Value; + } + else + { + individualPropertyValue = new WritableClientProperty + { + Convention = payloadConvention, + Value = payloadConvention.PayloadSerializer.DeserializeToType(JsonConvert.SerializeObject(componentProperty.Value)), + Version = twinCollection.Version, + }; + } + collectionDictionary.Add(componentProperty.Key, individualPropertyValue); + } + propertyCollectionToReturn.Add(property.Key, collectionDictionary); + } + else + { + var writableProperty = new WritableClientProperty + { + Convention = payloadConvention, + Value = payloadConvention.PayloadSerializer.DeserializeToType(JsonConvert.SerializeObject(propertyValueAsObject)), + Version = twinCollection.Version, + }; + propertyCollectionToReturn.Add(property.Key, writableProperty); + } } // The version information is not accessible via the enumerator, so assign it separately. propertyCollectionToReturn.Version = twinCollection.Version; @@ -306,7 +409,7 @@ internal static ClientPropertyCollection FromClientPropertiesAsDictionary(IDicti } else { - propertyCollectionToReturn.Add(property.Key, payloadConvention.PayloadSerializer.DeserializeToType(Newtonsoft.Json.JsonConvert.SerializeObject(property.Value))); + propertyCollectionToReturn.Add(property.Key, payloadConvention.PayloadSerializer.DeserializeToType(JsonConvert.SerializeObject(property.Value))); } } diff --git a/iothub/device/src/DeviceClient.cs b/iothub/device/src/DeviceClient.cs index 0bc6d5d1fc..daeacbc299 100644 --- a/iothub/device/src/DeviceClient.cs +++ b/iothub/device/src/DeviceClient.cs @@ -300,7 +300,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// /// /// You cannot Reject or Abandon messages over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The receive message or null if there was no message until the default timeout public Task ReceiveAsync() => InternalClient.ReceiveAsync(); @@ -312,7 +312,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// /// /// You cannot Reject or Abandon messages over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// A cancellation token to cancel the operation. /// Thrown when the operation has been canceled. @@ -326,7 +326,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// /// /// You cannot Reject or Abandon messages over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The receive message or null if there was no message until the specified time has elapsed public Task ReceiveAsync(TimeSpan timeout) => InternalClient.ReceiveAsync(timeout); @@ -380,7 +380,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Abandon a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message lockToken. /// The previously received message @@ -391,7 +391,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Abandon a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message lockToken. /// A cancellation token to cancel the operation. @@ -404,7 +404,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Abandon a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message. /// The lock identifier for the previously received message @@ -415,7 +415,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Abandon a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message. /// A cancellation token to cancel the operation. @@ -428,7 +428,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Reject a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message lockToken. /// The previously received message @@ -439,7 +439,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Reject a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// A cancellation token to cancel the operation. /// The message lockToken. @@ -452,7 +452,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Reject a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message. /// The lock identifier for the previously received message @@ -463,7 +463,7 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// /// You cannot Reject a message over MQTT protocol. - /// For more details, see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. + /// For more details, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messages-c2d#the-cloud-to-device-message-life-cycle. /// /// The message. /// A cancellation token to cancel the operation. @@ -527,7 +527,7 @@ public Task UploadToBlobAsync(string blobName, Stream source, CancellationToken InternalClient.UploadToBlobAsync(blobName, source, cancellationToken); /// - /// Get a file upload SAS URI which the Azure Storage SDK can use to upload a file to blob for this device. See this documentation for more details + /// Get a file upload SAS URI which the Azure Storage SDK can use to upload a file to blob for this device. See this documentation for more details /// /// The request details for getting the SAS URI, including the destination blob name. /// The cancellation token. @@ -536,7 +536,7 @@ public Task GetFileUploadSasUriAsync(FileUploadSasUriR InternalClient.GetFileUploadSasUriAsync(request, cancellationToken); /// - /// Notify IoT Hub that a device's file upload has finished. See this documentation for more details + /// Notify IoT Hub that a device's file upload has finished. See this documentation for more details /// /// The notification details, including if the file upload succeeded. /// The cancellation token. diff --git a/iothub/device/src/ModuleClient.cs b/iothub/device/src/ModuleClient.cs index 04a63de0ec..92dee49ee0 100644 --- a/iothub/device/src/ModuleClient.cs +++ b/iothub/device/src/ModuleClient.cs @@ -360,7 +360,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// /// Sends a batch of events to IoT hub. Use AMQP or HTTPs for a true batch operation. MQTT will just send the messages one after the other. - /// For more information on IoT Edge module routing + /// For more information on IoT Edge module routing /// /// The messages. /// The task containing the event @@ -368,7 +368,7 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// /// Sends a batch of events to IoT hub. Use AMQP or HTTPs for a true batch operation. MQTT will just send the messages one after the other. - /// For more information on IoT Edge module routing /// Sends a batch of events to IoT hub. Requires AMQP or AMQP over WebSockets. + /// For more information on IoT Edge module routing /// Sends a batch of events to IoT hub. Requires AMQP or AMQP over WebSockets. /// /// An IEnumerable set of Message objects. /// A cancellation token to cancel the operation. @@ -539,7 +539,7 @@ public Task SendEventAsync(string outputName, Message message, CancellationToken /// /// Sends a batch of events to IoT hub. Use AMQP or HTTPs for a true batch operation. MQTT will just send the messages one after the other. - /// For more information on IoT Edge module routing + /// For more information on IoT Edge module routing /// /// The output target for sending the given message /// A list of one or more messages to send @@ -550,7 +550,7 @@ public Task SendEventBatchAsync(string outputName, IEnumerable messages /// /// Sends a batch of events to IoT hub. Use AMQP or HTTPs for a true batch operation. MQTT will just send the messages one after the other. - /// For more information on IoT Edge module routing + /// For more information on IoT Edge module routing /// /// The output target for sending the given message /// A list of one or more messages to send diff --git a/iothub/device/src/NumericHelpers.cs b/iothub/device/src/NumericHelpers.cs deleted file mode 100644 index d9b836c90e..0000000000 --- a/iothub/device/src/NumericHelpers.cs +++ /dev/null @@ -1,45 +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.Globalization; - -namespace Microsoft.Azure.Devices.Client -{ - internal class NumericHelpers - { - internal static bool TryCastNumericTo(object input, out T result) - { - if (TryGetNumeric(input)) - { - try - { - result = (T)Convert.ChangeType(input, typeof(T), CultureInfo.InvariantCulture); - return true; - } - catch - { - } - } - - result = default; - return false; - } - - private static bool TryGetNumeric(object expression) - { - if (expression == null) - { - return false; - } - - return double.TryParse( - Convert.ToString( - expression, - CultureInfo.InvariantCulture), - NumberStyles.Any, - NumberFormatInfo.InvariantInfo, - out _); - } - } -} diff --git a/iothub/device/src/ObjectConversionHelpers.cs b/iothub/device/src/ObjectConversionHelpers.cs new file mode 100644 index 0000000000..eb327e43ad --- /dev/null +++ b/iothub/device/src/ObjectConversionHelpers.cs @@ -0,0 +1,82 @@ +// 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.Globalization; +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + internal class ObjectConversionHelpers + { + internal static bool TryCastOrConvert(object objectToCastOrConvert, PayloadConvention payloadConvention, out T value) + { + // If the object is of type T or can be cast to type T, go ahead and return it. + if (TryCast(objectToCastOrConvert, out value)) + { + return true; + } + + try + { + // If the cannot be cast to directly we need to try to convert it using the serializer. + // If it can be successfully converted, go ahead and return it. + value = payloadConvention.PayloadSerializer.ConvertFromObject(objectToCastOrConvert); + return true; + } + catch + { + } + + value = default; + return false; + } + + internal static bool TryCast(object objectToCast, out T value) + { + if (objectToCast is T valueRef + || TryCastNumericTo(objectToCast, out valueRef)) + { + value = valueRef; + return true; + } + + value = default; + return false; + } + + internal static bool TryCastNumericTo(object input, out T result) + { + if (TryGetNumeric(input)) + { + try + { + result = (T)Convert.ChangeType(input, typeof(T), CultureInfo.InvariantCulture); + return true; + } + catch + { + } + } + + result = default; + return false; + } + + private static bool TryGetNumeric(object expression) + { + if (expression == null) + { + return false; + } + + return double.TryParse( + Convert.ToString( + expression, + CultureInfo.InvariantCulture), + NumberStyles.Any, + NumberFormatInfo.InvariantInfo, + out _); + } + } +} diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index c9baeb4856..d4dcbc2295 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -127,28 +127,85 @@ public bool TryGetValue(string key, out T value) return false; } + // While retrieving the telemetry value from the collection, a simple dictionary indexer should work. + // While retrieving the property value from the collection: + // 1. A property collection constructed by the client application - can be retrieved using dictionary indexer. + // 2. Client property received through writable property update callbacks - stored internally as a WritableClientProperty. + // 3. Client property returned through GetClientProperties: + // a. Client reported properties sent by the client application in response to writable property update requests - stored as a JSON object + // and needs to be converted to an IWritablePropertyResponse implementation using the payload serializer. + // b. Client reported properties sent by the client application - stored as a JSON object + // and needs to be converted to the expected type using the payload serializer. + // c. Writable property update request received - stored as a JSON object + // and needs to be converted to the expected type using the payload serializer. if (Collection.ContainsKey(key)) { + object retrievedPropertyValue = Collection[key]; + // If the value associated with the key is null, then return true with the default value of the type passed in. - if (Collection[key] == null) + if (retrievedPropertyValue == null) { value = default; return true; } + // Case 1: // If the object is of type T or can be cast to type T, go ahead and return it. - if (Collection[key] is T valueRef - || NumericHelpers.TryCastNumericTo(Collection[key], out valueRef)) + if (ObjectConversionHelpers.TryCast(retrievedPropertyValue, out value)) { - value = valueRef; return true; } + // Case 2: + // Check if the retrieved value is a writable property update request + if (retrievedPropertyValue is WritableClientProperty writableClientProperty) + { + object writableClientPropertyValue = writableClientProperty.Value; + + // If the object is of type T or can be cast or converted to type T, go ahead and return it. + if (ObjectConversionHelpers.TryCastOrConvert(writableClientPropertyValue, Convention, out value)) + { + return true; + } + } + try { - // If the value cannot be cast to directly, we need to try to convert it using the serializer. + try + { + // Case 3a: + // Check if the retrieved value is a writable property update acknowledgment + var newtonsoftWritablePropertyResponse = Convention.PayloadSerializer.ConvertFromObject(retrievedPropertyValue); + + if (typeof(IWritablePropertyResponse).IsAssignableFrom(typeof(T))) + { + // If T is IWritablePropertyResponse the property value should be of type IWritablePropertyResponse as defined in the PayloadSerializer. + // We'll convert the json object to NewtonsoftJsonWritablePropertyResponse and then convert it to the appropriate IWritablePropertyResponse object. + value = (T)Convention.PayloadSerializer.CreateWritablePropertyResponse( + newtonsoftWritablePropertyResponse.Value, + newtonsoftWritablePropertyResponse.AckCode, + newtonsoftWritablePropertyResponse.AckVersion, + newtonsoftWritablePropertyResponse.AckDescription); + return true; + } + + var writablePropertyValue = newtonsoftWritablePropertyResponse.Value; + + // If the object is of type T or can be cast or converted to type T, go ahead and return it. + if (ObjectConversionHelpers.TryCastOrConvert(writablePropertyValue, Convention, out value)) + { + return true; + } + } + catch + { + // In case of an exception ignore it and continue. + } + + // Case 3b, 3c: + // If the value is neither a writable property nor can be cast to directly, we need to try to convert it using the serializer. // If it can be successfully converted, go ahead and return it. - value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); + value = Convention.PayloadSerializer.ConvertFromObject(retrievedPropertyValue); return true; } catch diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs index 65d46003b9..e899c55994 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportSettings.cs @@ -200,7 +200,7 @@ public bool CertificateRevocationCheck /// Setting a will message is a way for clients to notify other subscribed clients about ungraceful disconnects in an appropriate way. /// In response to the ungraceful disconnect, the service will send the last-will message to the configured telemetry channel. /// The telemetry channel can be either the default Events endpoint or a custom endpoint defined by IoT Hub routing. - /// For more details, refer to https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support#using-the-mqtt-protocol-directly-as-a-device. + /// For more details, refer to https://docs.microsoft.com/azure/iot-hub/iot-hub-mqtt-support#using-the-mqtt-protocol-directly-as-a-device. /// public bool HasWill { get; set; } @@ -209,7 +209,7 @@ public bool CertificateRevocationCheck /// /// /// The telemetry channel can be either the default Events endpoint or a custom endpoint defined by IoT Hub routing. - /// For more details, refer to https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support#using-the-mqtt-protocol-directly-as-a-device. + /// For more details, refer to https://docs.microsoft.com/azure/iot-hub/iot-hub-mqtt-support#using-the-mqtt-protocol-directly-as-a-device. /// public IWillMessage WillMessage { get; set; } diff --git a/iothub/device/src/WritableClientProperty.cs b/iothub/device/src/WritableClientProperty.cs new file mode 100644 index 0000000000..409c876048 --- /dev/null +++ b/iothub/device/src/WritableClientProperty.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Azure.Devices.Shared; + +namespace Microsoft.Azure.Devices.Client +{ + /// + /// The writable property update request received from service. + /// + /// + /// A writable property update request should be acknowledged by the device or module by sending a reported property. + /// This type contains a convenience method to format the reported property as per IoT Plug and Play convention. + /// For more details see . + /// + public class WritableClientProperty + { + internal WritableClientProperty() + { + } + + /// + /// The value of the writable property update request. + /// + public object Value { get; internal set; } + + /// + /// The version number associated with the writable property update request. + /// + public long Version { get; internal set; } + + internal PayloadConvention Convention { get; set; } + + /// + /// Creates a writable property update response that can be reported back to the service. + /// + /// + /// This writable property update response will contain the property value and version supplied in the writable property update request. + /// If you would like to construct your own writable property update response with custom value and version number, you can + /// create an instance of . + /// See for more details. + /// + /// An acknowledgment code that uses an HTTP status code. + /// An optional acknowledgment description. + /// A writable property update response that can be reported back to the service. + public IWritablePropertyResponse AcknowledgeWith(int statusCode, string description = default) + { + return Convention.PayloadSerializer.CreateWritablePropertyResponse(Value, statusCode, Version, description); + } + } +} diff --git a/iothub/device/tests/DeviceClientTests.cs b/iothub/device/tests/DeviceClientTests.cs index 5cb0672808..715b0ead00 100644 --- a/iothub/device/tests/DeviceClientTests.cs +++ b/iothub/device/tests/DeviceClientTests.cs @@ -107,7 +107,7 @@ public void DeviceClient_ParsmHostNameGatewayAuthMethodTransportArray_Works() } // This is for the scenario where an IoT Edge device is defined as the downstream device's transparent gateway. - // For more details, see https://docs.microsoft.com/en-us/azure/iot-edge/how-to-authenticate-downstream-device#retrieve-and-modify-connection-string + // For more details, see https://docs.microsoft.com/azure/iot-edge/how-to-authenticate-downstream-device#retrieve-and-modify-connection-string [TestMethod] public void DeviceClient_Params_GatewayAuthMethod_Works() { @@ -118,7 +118,7 @@ public void DeviceClient_Params_GatewayAuthMethod_Works() } // This is for the scenario where an IoT Edge device is defined as the downstream device's transparent gateway. - // For more details, see https://docs.microsoft.com/en-us/azure/iot-edge/how-to-authenticate-downstream-device#retrieve-and-modify-connection-string + // For more details, see https://docs.microsoft.com/azure/iot-edge/how-to-authenticate-downstream-device#retrieve-and-modify-connection-string [TestMethod] public void DeviceClient_ParamsGatewayAuthMethodTransport_Works() { @@ -129,7 +129,7 @@ public void DeviceClient_ParamsGatewayAuthMethodTransport_Works() } // This is for the scenario where an IoT Edge device is defined as the downstream device's transparent gateway. - // For more details, see https://docs.microsoft.com/en-us/azure/iot-edge/how-to-authenticate-downstream-device#retrieve-and-modify-connection-string + // For more details, see https://docs.microsoft.com/azure/iot-edge/how-to-authenticate-downstream-device#retrieve-and-modify-connection-string [TestMethod] public void DeviceClient_ParamsGatewayAuthMethodTransportArray_Works() { diff --git a/iothub/device/tests/NumericHelpersTests.cs b/iothub/device/tests/ObjectCastHelpersTests.cs similarity index 88% rename from iothub/device/tests/NumericHelpersTests.cs rename to iothub/device/tests/ObjectCastHelpersTests.cs index caa3ad28e8..52cb0905cb 100644 --- a/iothub/device/tests/NumericHelpersTests.cs +++ b/iothub/device/tests/ObjectCastHelpersTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Devices.Client.Test { [TestClass] [TestCategory("Unit")] - public class NumericHelpersTests + public class ObjectCastHelpersTests { [TestMethod] public void CanConvertNumericTypes() @@ -27,7 +27,7 @@ public void CanConvertNumericTypes() private void TestNumericConversion(object input, bool canConvertExpected, T resultExpected) { - bool canConvertActual = NumericHelpers.TryCastNumericTo(input, out T result); + bool canConvertActual = ObjectConversionHelpers.TryCastNumericTo(input, out T result); canConvertActual.Should().Be(canConvertExpected); result.Should().Be(resultExpected); diff --git a/iothub/service/src/Common/Data/AccessRights.cs b/iothub/service/src/Common/Data/AccessRights.cs index 9887752de9..93f019e0ad 100644 --- a/iothub/service/src/Common/Data/AccessRights.cs +++ b/iothub/service/src/Common/Data/AccessRights.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Devices.Common.Data { /// /// Shared access policy permissions of IoT hub. - /// For more information, see . + /// For more information, see . /// [Flags] [JsonConverter(typeof(StringEnumConverter))] @@ -19,14 +19,14 @@ public enum AccessRights /// /// Grants read access to the identity registry. /// Identity registry stores information about the devices and modules permitted to connect to the IoT hub. - /// For more information, see . + /// For more information, see . /// RegistryRead = 1, /// /// Grants read and write access to the identity registry. /// Identity registry stores information about the devices and modules permitted to connect to the IoT hub. - /// For more information, see . + /// For more information, see . /// RegistryWrite = RegistryRead | 2, diff --git a/iothub/service/src/DigitalTwin/DigitalTwinClient.cs b/iothub/service/src/DigitalTwin/DigitalTwinClient.cs index 9f434063d8..ab2d7548f4 100644 --- a/iothub/service/src/DigitalTwin/DigitalTwinClient.cs +++ b/iothub/service/src/DigitalTwin/DigitalTwinClient.cs @@ -56,7 +56,7 @@ public static DigitalTwinClient CreateFromConnectionString(string connectionStri /// The delegating handlers to add to the http client pipeline. You can add handlers for tracing, implementing a retry strategy, routing requests through a proxy, etc. /// An instance of . /// - /// For more information on configuring IoT hub with Azure Active Directory, see + /// For more information on configuring IoT hub with Azure Active Directory, see /// public static DigitalTwinClient Create( string hostName, @@ -124,7 +124,7 @@ public virtual async Task> GetDi /// /// Updates a digital twin. - /// For further information on how to create the json-patch, see + /// For further information on how to create the json-patch, see /// /// The Id of the digital twin. /// The application/json-patch+json operations to be performed on the specified digital twin. diff --git a/iothub/service/src/DigitalTwin/Serialization/UpdateOperationsUtility.cs b/iothub/service/src/DigitalTwin/Serialization/UpdateOperationsUtility.cs index a1639436f7..8b12d44899 100644 --- a/iothub/service/src/DigitalTwin/Serialization/UpdateOperationsUtility.cs +++ b/iothub/service/src/DigitalTwin/Serialization/UpdateOperationsUtility.cs @@ -23,7 +23,7 @@ public class UpdateOperationsUtility /// /// Include an add property operation. - /// Learn more about managing digital twins here . + /// Learn more about managing digital twins here . /// /// /// @@ -62,7 +62,7 @@ public void AppendAddPropertyOp(string path, object value) /// /// Include a replace property operation. - /// Learn more about managing digital twins here . + /// Learn more about managing digital twins here . /// /// /// @@ -101,7 +101,7 @@ public void AppendReplacePropertyOp(string path, object value) /// /// Include a remove operation. - /// Learn more about managing digital twins here . + /// Learn more about managing digital twins here . /// /// /// @@ -145,7 +145,7 @@ public void AppendRemoveOp(string path) /// /// Include an add component operation. - /// Learn more about managing digital twins here . + /// Learn more about managing digital twins here . /// /// /// This utility appends the "$metadata" identifier to the property values, @@ -182,7 +182,7 @@ public void AppendAddComponentOp(string path, Dictionary propert /// /// Include a replace component operation. - /// Learn more about managing digital twins here . + /// Learn more about managing digital twins here . /// /// /// This utility appends the "$metadata" identifier to the property values, diff --git a/iothub/service/src/FeedbackStatusCode.cs b/iothub/service/src/FeedbackStatusCode.cs index 617bd20bf1..74a99ee213 100644 --- a/iothub/service/src/FeedbackStatusCode.cs +++ b/iothub/service/src/FeedbackStatusCode.cs @@ -14,32 +14,32 @@ public enum FeedbackStatusCode { /// /// Indicates that the cloud-to-device message was successfully delivered to the device. - /// For information on cloud-to-device message life cycle, see . + /// For information on cloud-to-device message life cycle, see . /// Success = 0, /// /// Indicates that the cloud-to-device message expired before it could be delivered to the device. - /// For information on cloud-to-device message life cycle, see . + /// For information on cloud-to-device message life cycle, see . /// Expired = 1, /// /// Indicates that the cloud-to-device message has been placed in a dead-lettered state. /// This happens when the message reaches the maximum count for the number of times it can transition between enqueued and invisible states. - /// For information on cloud-to-device message life cycle, see . + /// For information on cloud-to-device message life cycle, see . /// DeliveryCountExceeded = 2, /// /// Indicates that the cloud-to-device message was rejected by the device. - /// For information on cloud-to-device message life cycle, see . + /// For information on cloud-to-device message life cycle, see . /// Rejected = 3, /// /// Indicates that the cloud-to-device message was purged from IoT Hub. - /// For information on cloud-to-device message life cycle, see . + /// For information on cloud-to-device message life cycle, see . /// Purged = 4 } diff --git a/iothub/service/src/JobClient/JobClient.cs b/iothub/service/src/JobClient/JobClient.cs index 32421512b4..18b97cea4c 100644 --- a/iothub/service/src/JobClient/JobClient.cs +++ b/iothub/service/src/JobClient/JobClient.cs @@ -100,7 +100,7 @@ public static JobClient CreateFromConnectionString(string connectionString, Http /// The HTTP transport settings. /// An instance of . /// - /// For more information on configuring IoT hub with Azure Active Directory, see + /// For more information on configuring IoT hub with Azure Active Directory, see /// public static JobClient Create( string hostName, diff --git a/iothub/service/src/JobProperties.cs b/iothub/service/src/JobProperties.cs index fbab545b2b..ca5bb057ee 100644 --- a/iothub/service/src/JobProperties.cs +++ b/iothub/service/src/JobProperties.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.Devices { /// /// Contains properties of a Job. - /// See online documentation for more infomration. + /// See online documentation for more infomration. /// public class JobProperties { diff --git a/iothub/service/src/ManagedIdentity.cs b/iothub/service/src/ManagedIdentity.cs index 2fa2467be1..8ef757b1a5 100644 --- a/iothub/service/src/ManagedIdentity.cs +++ b/iothub/service/src/ManagedIdentity.cs @@ -10,8 +10,8 @@ namespace Microsoft.Azure.Devices { /// /// The managed identity used to access the storage account for IoT hub import and export jobs. - /// For more information on managed identity configuration on IoT hub, see . - /// For more information on managed identities, see + /// For more information on managed identity configuration on IoT hub, see . + /// For more information on managed identities, see /// public class ManagedIdentity { diff --git a/iothub/service/src/MessageSystemPropertyNames.cs b/iothub/service/src/MessageSystemPropertyNames.cs index 86c559a4d8..d768fade28 100644 --- a/iothub/service/src/MessageSystemPropertyNames.cs +++ b/iothub/service/src/MessageSystemPropertyNames.cs @@ -39,7 +39,7 @@ public static class MessageSystemPropertyNames /// /// The number of times a message can transition between the Enqueued and Invisible states. /// After the maximum number of transitions, the IoT hub sets the state of the message to dead-lettered. - /// For more information, see + /// For more information, see /// public const string DeliveryCount = "iothub-deliverycount"; diff --git a/iothub/service/src/RegistryManager.cs b/iothub/service/src/RegistryManager.cs index ed6882f3d1..7568d07f2a 100644 --- a/iothub/service/src/RegistryManager.cs +++ b/iothub/service/src/RegistryManager.cs @@ -134,7 +134,7 @@ public static RegistryManager CreateFromConnectionString(string connectionString /// The HTTP transport settings. /// An instance of . /// - /// For more information on configuring IoT hub with Azure Active Directory, see + /// For more information on configuring IoT hub with Azure Active Directory, see /// public static RegistryManager Create( string hostName, diff --git a/iothub/service/src/ServiceClient.cs b/iothub/service/src/ServiceClient.cs index 5ee7c3856a..d5ba708c8a 100644 --- a/iothub/service/src/ServiceClient.cs +++ b/iothub/service/src/ServiceClient.cs @@ -138,7 +138,7 @@ public static ServiceClient CreateFromConnectionString(string connectionString, /// The options that allow configuration of the service client instance during initialization. /// An instance of . /// - /// For more information on configuring IoT hub with Azure Active Directory, see + /// For more information on configuring IoT hub with Azure Active Directory, see /// public static ServiceClient Create( string hostName, @@ -385,7 +385,7 @@ public virtual Task PurgeMessageQueueAsync(string devic /// /// 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 . + /// For more information see . /// /// An instance of . public virtual FeedbackReceiver GetFeedbackReceiver() @@ -396,7 +396,7 @@ public virtual FeedbackReceiver GetFeedbackReceiver() /// /// Get the which can deliver notifications for file upload operations. /// This call is made over AMQP. - /// For more information see . + /// For more information see . /// /// An instance of . public virtual FileNotificationReceiver GetFileNotificationReceiver() diff --git a/provisioning/device/src/PlugAndPlay/PnpConvention.cs b/provisioning/device/src/PlugAndPlay/PnpConvention.cs index b3b29e0645..4a64f8244b 100644 --- a/provisioning/device/src/PlugAndPlay/PnpConvention.cs +++ b/provisioning/device/src/PlugAndPlay/PnpConvention.cs @@ -15,7 +15,7 @@ public static class PnpConvention /// /// /// For more information on device provisioning service and plug and play compatibility, - /// and PnP device certification, see . + /// and PnP device certification, see . /// The DPS payload should be in the format: /// /// { diff --git a/readme.md b/readme.md index 1436873e22..87c34d3955 100644 --- a/readme.md +++ b/readme.md @@ -51,7 +51,7 @@ Due to security considerations, build logs are not publicly available. | Microsoft.Azure.Devices.DigitalTwin.Service | N/A | [![NuGet][pnp-service-prerelease]][pnp-service-nuget] | > Note: -> Device streaming feature is not being included in our newer preview releases as there is no active development going on in the service. For more details on the feature, see [here](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-device-streams-overview). It is not recommended to take dependency on preview nugets for production applications as breaking changes can be introduced in preview nugets. +> Device streaming feature is not being included in our newer preview releases as there is no active development going on in the service. For more details on the feature, see [here](https://docs.microsoft.com/azure/iot-hub/iot-hub-device-streams-overview). It is not recommended to take dependency on preview nugets for production applications as breaking changes can be introduced in preview nugets. > > The feature has not been included in any preview release after [2020-10-14](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/preview_2020-10-14). However, the feature is still available under previews/deviceStreaming branch. > diff --git a/shared/src/NewtonsoftJsonPayloadSerializer.cs b/shared/src/NewtonsoftJsonPayloadSerializer.cs index b38063178b..349c76676b 100644 --- a/shared/src/NewtonsoftJsonPayloadSerializer.cs +++ b/shared/src/NewtonsoftJsonPayloadSerializer.cs @@ -54,10 +54,23 @@ public override bool TryGetNestedObjectValue(object nestedObject, string prop { return false; } - if (((JObject)nestedObject).TryGetValue(propertyName, out JToken element)) + + try + { + // The supplied nested object is either a JObject or the string representation of a JObject. + JObject nestedObjectAsJObject = nestedObject.GetType() == typeof(string) + ? DeserializeToType((string)nestedObject) + : nestedObject as JObject; + + if (nestedObjectAsJObject != null && nestedObjectAsJObject.TryGetValue(propertyName, out JToken element)) + { + outValue = element.ToObject(); + return true; + } + } + catch { - outValue = element.ToObject(); - return true; + // Catch and ignore any exceptions caught } return false; } diff --git a/shared/src/PayloadSerializer.cs b/shared/src/PayloadSerializer.cs index 9fced3233e..15e7ee435f 100644 --- a/shared/src/PayloadSerializer.cs +++ b/shared/src/PayloadSerializer.cs @@ -56,7 +56,8 @@ public abstract class PayloadSerializer /// An example of this would be a property under the component. /// /// The type to convert the retrieved property to. - /// The object that might contain the nested property. + /// The object that might contain the nested property. + /// This needs to be in the json object equivalent format as required by the serializer or the string representation of it. /// The name of the property to be retrieved. /// True if the nested object contains an element with the specified key; otherwise, it returns false. /// diff --git a/supported_platforms.md b/supported_platforms.md index 0944a25974..a963a60020 100644 --- a/supported_platforms.md +++ b/supported_platforms.md @@ -35,7 +35,7 @@ OS name: "windows server 2016", version: "10.0", arch: "amd64", family: "windows ## Ubuntu 1604 -Note that, while we only directly test on Ubuntu 1604, we do generally support other [Linux distributions supported by .NET core](https://docs.microsoft.com/en-us/dotnet/core/install/linux). +Note that, while we only directly test on Ubuntu 1604, we do generally support other [Linux distributions supported by .NET core](https://docs.microsoft.com/dotnet/core/install/linux). Nightly test platform details: From 9e782d28f244f8e62e414dd867c9e242155f17d1 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 2 Sep 2021 11:58:29 -0700 Subject: [PATCH 64/77] fix(iot-device): Fix params passed into ObjectDisposedException --- iothub/device/src/AuthenticationWithTokenRefresh.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iothub/device/src/AuthenticationWithTokenRefresh.cs b/iothub/device/src/AuthenticationWithTokenRefresh.cs index 8c221d23cc..a682868796 100644 --- a/iothub/device/src/AuthenticationWithTokenRefresh.cs +++ b/iothub/device/src/AuthenticationWithTokenRefresh.cs @@ -100,7 +100,7 @@ public async Task GetTokenAsync(string iotHub) { if (_isDisposed) { - throw new ObjectDisposedException("The authentication method instance has already been disposed, so this client is no longer usable. " + + throw new ObjectDisposedException(GetType().Name, "The authentication method instance has already been disposed, so this client is no longer usable. " + "Please close and dispose your current client instance. To continue carrying out operations from your device/ module, " + "create a new authentication method instance and use it for reinitializing your client."); } From 37a68f6109d671064ac0ea63ec0d3255dea4cc83 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 15 Sep 2021 08:54:59 -0700 Subject: [PATCH 65/77] fix(githib): Update github issues template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 981ce363a2..fda342332a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -51,5 +51,5 @@ Please be as detailed as possible: which feature has a problem, how often does i Please remove any connection string information! ## Console log of the issue -Consider setting the DEBUG environment variable to '*'. This will produce a much more verbose output that will help debugging +Follow the instructions [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/tools/CaptureLogs) to capture SDK logs. Don't forget to remove any connection string information! From 322b574d2c19c71241b52060263fc6cca5d81a53 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Wed, 15 Sep 2021 14:51:54 -0700 Subject: [PATCH 66/77] Add support for .NET 5 (#2169) * feat(all): Add support for .NET 5.0 * refactor(all): Update APIs to follow .NET 5.0 patterns * refactor(e2e-tests): Update E2E tests to follow .NET 5.0 patterns * refactor(all): Simplify disposal of IDisposable X509Certificate * fix(vsts): Update the location for log analytics workspace resource * refactor(e2e-tests): Dispose IDisposable types * fix(e2e-tests): Dispose IDisposable types created - hmac --- common/src/HttpContentExtensions.cs | 65 +++++++ common/src/HttpMessageHelper.cs | 35 ++++ common/src/StreamExtensions.cs | 24 +++ common/src/TaskCompletionSource.cs | 27 +++ common/src/service/HttpClientHelper.cs | 59 ++----- e2e/test/E2EMsTestBase.cs | 4 - e2e/test/E2ETests.csproj | 4 +- e2e/test/config/TestConfiguration.IoTHub.cs | 2 +- e2e/test/helpers/CustomWebProxy.cs | 2 +- e2e/test/helpers/StorageContainer.cs | 2 +- e2e/test/helpers/TestDevice.cs | 39 ++++- e2e/test/helpers/TestDeviceCallbackHandler.cs | 4 +- e2e/test/helpers/TestModule.cs | 2 +- e2e/test/helpers/logging/LoggedTestMethod.cs | 2 +- e2e/test/helpers/logging/TestLogger.cs | 6 +- e2e/test/helpers/templates/FaultInjection.cs | 17 +- .../FaultInjectionPoolingOverAmqp.cs | 16 +- e2e/test/helpers/templates/PoolingOverAmqp.cs | 16 +- ...enticationWithTokenRefreshDisposalTests.cs | 3 + .../ConnectionStatusChangeHandlerTests.cs | 2 +- .../DeviceClientX509AuthenticationE2ETests.cs | 120 +++++++------ e2e/test/iothub/DeviceTokenRefreshE2ETests.cs | 159 +++++++++--------- e2e/test/iothub/FileUploadE2ETests.cs | 103 +++++++----- .../iothub/FileUploadFaultInjectionTests.cs | 7 +- e2e/test/iothub/NoRetryE2ETests.cs | 4 +- ...eSecurityCenterForIoTLogAnalyticsClient.cs | 12 +- ...rityCenterForIoTSecurityMessageE2ETests.cs | 6 +- ...ssageReceiveFaultInjectionPoolAmqpTests.cs | 20 +-- ....MessageSendFaultInjectionPoolAmqpTests.cs | 8 +- .../messaging/MessageFeedbackE2ETests.cs | 2 +- .../messaging/MessageReceiveE2ETests.cs | 34 ++-- .../iothub/messaging/MessageSendE2ETests.cs | 8 +- ...Tests.MethodFaultInjectionPoolAmqpTests.cs | 27 +-- e2e/test/iothub/method/MethodE2ETests.cs | 16 +- .../iothub/service/BulkOperationsE2ETests.cs | 12 +- .../service/DigitalTwinClientE2ETests.cs | 4 +- .../IoTHubCertificateValidationE2ETest.cs | 16 +- .../service/IoTHubServiceProxyE2ETests.cs | 2 +- e2e/test/iothub/service/PnpServiceTests.cs | 27 ++- .../iothub/service/RegistryManagerE2ETests.cs | 58 +++---- .../RegistryManagerExportDevicesTests.cs | 2 +- .../RegistryManagerImportDevicesTests.cs | 4 +- .../iothub/service/ServiceClientE2ETests.cs | 35 ++-- ...qpTests.TwinFaultInjectionPoolAmqpTests.cs | 31 +--- e2e/test/iothub/twin/TwinE2ETests.cs | 46 ++--- .../iothub/twin/TwinFaultInjectionTests.cs | 4 +- ...rovisioningCertificateValidationE2ETest.cs | 7 +- e2e/test/provisioning/ProvisioningE2ETests.cs | 82 +++++---- .../ProvisioningServiceClientE2ETests.cs | 8 +- .../provisioning/ReprovisioningE2ETests.cs | 96 ++++++----- ...DeviceAuthenticationWithX509Certificate.cs | 8 +- .../src/Microsoft.Azure.Devices.Client.csproj | 13 +- .../Transport/HttpUdsMessageHandler.cs | 9 +- .../src/Transport/AmqpIot/AmqpIotConnector.cs | 7 +- .../device/src/Transport/HttpClientHelper.cs | 5 +- .../src/Transport/HttpTransportHandler.cs | 2 +- .../src/Transport/Mqtt/MqttIotHubAdapter.cs | 15 +- .../Transport/Mqtt/MqttTransportHandler.cs | 11 +- .../src/Transport/Mqtt/PublishWorkItem.cs | 11 +- .../src/Transport/Mqtt/SimpleWorkQueue.cs | 11 +- ...icrosoft.Azure.Devices.Client.Tests.csproj | 4 +- ...RetryDelegatingHandlerImplicitOpenTests.cs | 6 +- .../src/Microsoft.Azure.Devices.csproj | 10 +- .../Microsoft.Azure.Devices.Tests.csproj | 4 +- ...t.Azure.Devices.Provisioning.Client.csproj | 2 +- ...e.Devices.Provisioning.Client.Tests.csproj | 2 +- .../service/src/Contract/ContractApiHttp.cs | 2 +- ....Azure.Devices.Provisioning.Service.csproj | 3 +- .../Config/X509CertificateWithInfoTests.cs | 10 +- .../tests/Config/X509CertificatesTests.cs | 22 ++- ....Devices.Provisioning.Service.Tests.csproj | 2 +- ...Devices.Provisioning.Transport.Amqp.csproj | 2 +- ...s.Provisioning.Transport.Amqp.Tests.csproj | 2 +- .../ProvisioningErrorDetailsAmqpTests.cs | 96 ++++++----- .../http/src/Generated/RuntimeRegistration.cs | 15 +- ...Devices.Provisioning.Transport.Http.csproj | 5 +- .../transport/http/src/TPM/TpmCredentials.cs | 6 + .../http/src/TPM/TpmDelegatingHandler.cs | 13 +- ...s.Provisioning.Transport.Http.Tests.csproj | 2 +- ...Devices.Provisioning.Transport.Mqtt.csproj | 2 +- ...s.Provisioning.Transport.Mqtt.Tests.csproj | 2 +- .../SecurityProviderTpmSimulator.cs | 23 ++- .../SecurityProviderTpmSimulator.csproj | 3 +- ...e.Devices.Provisioning.Security.Tpm.csproj | 2 +- ...ces.Provisioning.Security.Tpm.Tests.csproj | 2 +- .../src/Microsoft.Azure.Devices.Shared.csproj | 4 +- ...icrosoft.Azure.Devices.Shared.Tests.csproj | 4 +- supported_platforms.md | 15 +- .../TLS protocol tests.csproj | 4 +- vsts/test-release-nuget.yaml | 10 +- vsts/vsts.yaml | 13 +- 91 files changed, 967 insertions(+), 708 deletions(-) create mode 100644 common/src/HttpContentExtensions.cs create mode 100644 common/src/HttpMessageHelper.cs create mode 100644 common/src/StreamExtensions.cs create mode 100644 common/src/TaskCompletionSource.cs diff --git a/common/src/HttpContentExtensions.cs b/common/src/HttpContentExtensions.cs new file mode 100644 index 0000000000..e5d1329ed6 --- /dev/null +++ b/common/src/HttpContentExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// Extensions added to simplify the usage of APIs based on the .NET implementation used. + /// + internal static class HttpContentExtensions + { + internal static async Task CopyToStreamAsync(this HttpContent content, Stream stream, CancellationToken cancellationToken) + { +#if NET5_0 + await content.CopyToAsync(stream, cancellationToken).ConfigureAwait(false); +#else + // .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it. + _ = cancellationToken; + + await content.CopyToAsync(stream).ConfigureAwait(false); +#endif + } + + internal static Task ReadHttpContentAsStream(this HttpContent httpContent, CancellationToken cancellationToken) + { +#if NET5_0 + return httpContent.ReadAsStreamAsync(cancellationToken); +#else + // .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it. + _ = cancellationToken; + + return httpContent.ReadAsStreamAsync(); +#endif + } + + internal static Task ReadHttpContentAsByteArrayAsync(this HttpContent content, CancellationToken cancellationToken) + { +#if NET5_0 + return content.ReadAsByteArrayAsync(cancellationToken); +#else + // .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it. + _ = cancellationToken; + + return content.ReadAsByteArrayAsync(); +#endif + } + + internal static Task ReadHttpContentAsStringAsync(this HttpContent content, CancellationToken cancellationToken) + { +#if NET5_0 + return content.ReadAsStringAsync(cancellationToken); +#else + // .NET implementations < .NET 5.0 do not support CancellationTokens for HttpContent APIs, so we will discard it. + _ = cancellationToken; + + return content.ReadAsStringAsync(); +#endif + } + + } +} \ No newline at end of file diff --git a/common/src/HttpMessageHelper.cs b/common/src/HttpMessageHelper.cs new file mode 100644 index 0000000000..eae670e207 --- /dev/null +++ b/common/src/HttpMessageHelper.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Net.Http; +using System.Text; +using Newtonsoft.Json; + +#if NET451 +using System.Net.Http.Formatting; +#endif + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// A helper class to simplify operations with Http messages based on the .NET implementation used. + /// + internal static class HttpMessageHelper + { +#if NET451 + private static readonly JsonMediaTypeFormatter s_jsonFormatter = new JsonMediaTypeFormatter(); +#else + private const string ApplicationJson = "application/json"; +#endif + + internal static void SetHttpRequestMessageContent(HttpRequestMessage requestMessage, T entity) + { +#if NET451 + requestMessage.Content = new ObjectContent(entity, s_jsonFormatter); +#else + string str = JsonConvert.SerializeObject(entity); + requestMessage.Content = new StringContent(str, Encoding.UTF8, ApplicationJson); +#endif + } + } +} \ No newline at end of file diff --git a/common/src/StreamExtensions.cs b/common/src/StreamExtensions.cs new file mode 100644 index 0000000000..af353bad64 --- /dev/null +++ b/common/src/StreamExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// Extensions added to simplify the usage of APIs based on the .NET implementation used. + /// + internal static class StreamExtensions + { + internal static async Task WriteToStreamAsync(this Stream stream, byte[] requestBytes, CancellationToken cancellationToken) + { +#if NET451 || NET472 || NETSTANDARD2_0 + await stream.WriteAsync(requestBytes, 0, requestBytes.Length, cancellationToken).ConfigureAwait(false); +#else + await stream.WriteAsync(requestBytes, cancellationToken).ConfigureAwait(false); +#endif + } + } +} \ No newline at end of file diff --git a/common/src/TaskCompletionSource.cs b/common/src/TaskCompletionSource.cs new file mode 100644 index 0000000000..73ca5ca5ce --- /dev/null +++ b/common/src/TaskCompletionSource.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +namespace Microsoft.Azure.Devices.Shared +{ + /// + /// A implementation that returns a when completed. + /// + /// + /// Represents the producer side of a unbound to a delegate, providing access to the consumer side through the property. + /// This is used for .NET implementations lower than .NET 5.0, which lack a native implementation of the non-generic TaskCompletionSource. + /// + internal sealed class TaskCompletionSource : TaskCompletionSource + { + public TaskCompletionSource() + { + } + + public bool TrySetResult() => TrySetResult(true); + + public void SetResult() => SetResult(true); + + public override string ToString() => $"TaskCompletionSource[status: {Task.Status}]"; + } +} \ No newline at end of file diff --git a/common/src/service/HttpClientHelper.cs b/common/src/service/HttpClientHelper.cs index a3ac7fccb3..5cddb9f422 100644 --- a/common/src/service/HttpClientHelper.cs +++ b/common/src/service/HttpClientHelper.cs @@ -10,7 +10,6 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Common; @@ -19,19 +18,13 @@ using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; -#if NET451 -using System.Net.Http.Formatting; -#endif +using static Microsoft.Azure.Devices.Shared.HttpMessageHelper; namespace Microsoft.Azure.Devices { internal sealed class HttpClientHelper : IHttpClientHelper { private const string ApplicationJson = "application/json"; - -#if NET451 - static readonly JsonMediaTypeFormatter JsonFormatter = new JsonMediaTypeFormatter(); -#endif private readonly Uri _baseAddress; private readonly IAuthorizationHeaderProvider _authenticationHeaderProvider; private readonly IReadOnlyDictionary>> _defaultErrorMapping; @@ -179,12 +172,8 @@ await ExecuteAsync( (requestMsg, token) => { InsertEtag(requestMsg, entity, operationType); -#if NET451 - requestMsg.Content = new ObjectContent(entity, JsonFormatter); -#else - string str = JsonConvert.SerializeObject(entity); - requestMsg.Content = new StringContent(str, Encoding.UTF8, ApplicationJson); -#endif + SetHttpRequestMessageContent(requestMsg, entity); + return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync(httpClient, token).ConfigureAwait(false), @@ -208,12 +197,7 @@ await ExecuteAsync( new Uri(_baseAddress, requestUri), (requestMsg, token) => { -#if NET451 - requestMsg.Content = new ObjectContent(entity, JsonFormatter); -#else - string str = JsonConvert.SerializeObject(entity); - requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson); -#endif + SetHttpRequestMessageContent(requestMsg, entity); return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync(httpClient, token).ConfigureAwait(false), @@ -237,12 +221,8 @@ await ExecuteAsync( (requestMsg, token) => { InsertEtag(requestMsg, etag, operationType); -#if NET451 - requestMsg.Content = new ObjectContent(entity, JsonFormatter); -#else - string str = Newtonsoft.Json.JsonConvert.SerializeObject(entity); - requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson); -#endif + SetHttpRequestMessageContent(requestMsg, entity); + return Task.FromResult(0); }, null, @@ -267,12 +247,8 @@ await ExecuteAsync( { // TODO: skintali: Use string etag when service side changes are ready InsertEtag(requestMsg, etag, operationType); -#if NET451 - requestMsg.Content = new ObjectContent(entity, JsonFormatter); -#else - string str = JsonConvert.SerializeObject(entity); - requestMsg.Content = new StringContent(str, Encoding.UTF8, ApplicationJson); -#endif + SetHttpRequestMessageContent(requestMsg, entity); + return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync(httpClient, token).ConfigureAwait(false), @@ -291,12 +267,8 @@ await ExecuteAsync( (requestMsg, token) => { InsertEtag(requestMsg, etag, PutOperationType.UpdateEntity); -#if NET451 - requestMsg.Content = new ObjectContent(entity, JsonFormatter); -#else - string str = JsonConvert.SerializeObject(entity); - requestMsg.Content = new StringContent(str, Encoding.UTF8, ApplicationJson); -#endif + SetHttpRequestMessageContent(requestMsg, entity); + return Task.FromResult(0); }, null, @@ -317,12 +289,8 @@ await ExecuteAsync( (requestMsg, token) => { InsertEtag(requestMsg, etag, putOperationType); -#if NET451 - requestMsg.Content = new ObjectContent(entity, JsonFormatter); -#else - string str = JsonConvert.SerializeObject(entity); - requestMsg.Content = new StringContent(str, System.Text.Encoding.UTF8, ApplicationJson); -#endif + SetHttpRequestMessageContent(requestMsg, entity); + return Task.FromResult(0); }, async (httpClient, token) => result = await ReadResponseMessageAsync(httpClient, token).ConfigureAwait(false), @@ -342,9 +310,10 @@ private static async Task ReadResponseMessageAsync(HttpResponseMessage mes #if NET451 T entity = await message.Content.ReadAsAsync(token).ConfigureAwait(false); #else - string str = await message.Content.ReadAsStringAsync().ConfigureAwait(false); + string str = await message.Content.ReadHttpContentAsStringAsync(token).ConfigureAwait(false); T entity = JsonConvert.DeserializeObject(str); #endif + // Etag in the header is considered authoritative var eTagHolder = entity as IETagHolder; if (eTagHolder != null) diff --git a/e2e/test/E2EMsTestBase.cs b/e2e/test/E2EMsTestBase.cs index 9a06aef960..9d33d823c5 100644 --- a/e2e/test/E2EMsTestBase.cs +++ b/e2e/test/E2EMsTestBase.cs @@ -5,10 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Tracing; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/e2e/test/E2ETests.csproj b/e2e/test/E2ETests.csproj index f025a01846..a16db962d7 100644 --- a/e2e/test/E2ETests.csproj +++ b/e2e/test/E2ETests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18;net472;net451 - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18;net472;net451 + net5.0;netcoreapp3.1;netcoreapp2.1.18 8.0 true false diff --git a/e2e/test/config/TestConfiguration.IoTHub.cs b/e2e/test/config/TestConfiguration.IoTHub.cs index 768bb1eb97..2e2a9264ec 100644 --- a/e2e/test/config/TestConfiguration.IoTHub.cs +++ b/e2e/test/config/TestConfiguration.IoTHub.cs @@ -119,7 +119,7 @@ private static string GenerateSasToken(string resourceUri, string sharedAccessKe string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry; - HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(sharedAccessKey)); + using var hmac = new HMACSHA256(Convert.FromBase64String(sharedAccessKey)); string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); // SharedAccessSignature sr=ENCODED(dh://myiothub.azure-devices.net/a/b/c?myvalue1=a)&sig=&se=[&skn=] diff --git a/e2e/test/helpers/CustomWebProxy.cs b/e2e/test/helpers/CustomWebProxy.cs index bf242d94d7..b0aea21bf2 100644 --- a/e2e/test/helpers/CustomWebProxy.cs +++ b/e2e/test/helpers/CustomWebProxy.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Helpers public class CustomWebProxy : IWebProxy { private readonly MsTestLogger _logger; - private long _counter = 0; + private long _counter; public CustomWebProxy(MsTestLogger logger) { diff --git a/e2e/test/helpers/StorageContainer.cs b/e2e/test/helpers/StorageContainer.cs index ac450b81cc..d763128d2f 100644 --- a/e2e/test/helpers/StorageContainer.cs +++ b/e2e/test/helpers/StorageContainer.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Devices.E2ETests.Helpers /// public class StorageContainer : IDisposable { - private bool _disposed = false; + private bool _disposed; public string ContainerName { get; } public Uri Uri { get; private set; } diff --git a/e2e/test/helpers/TestDevice.cs b/e2e/test/helpers/TestDevice.cs index 215100d669..659e5fd770 100644 --- a/e2e/test/helpers/TestDevice.cs +++ b/e2e/test/helpers/TestDevice.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Security.Cryptography.X509Certificates; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -23,7 +24,7 @@ public enum ConnectionStringAuthScope Device } - public class TestDevice + public class TestDevice : IDisposable { private const int MaxRetryCount = 5; private static readonly HashSet s_retryableExceptions = new HashSet { typeof(ThrottlingException) }; @@ -35,6 +36,8 @@ public class TestDevice maxBackoff: TimeSpan.FromSeconds(10), deltaBackoff: TimeSpan.FromMilliseconds(100)); + private X509Certificate2 _authCertificate; + private static MsTestLogger s_logger; private TestDevice(Device device, Client.IAuthenticationMethod authenticationMethod) @@ -72,12 +75,14 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str string deviceName = "E2E_" + prefix + Guid.NewGuid(); // Delete existing devices named this way and create a new one. - using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); s_logger.Trace($"{nameof(GetTestDeviceAsync)}: Creating device {deviceName} with type {type}."); Client.IAuthenticationMethod auth = null; var requestDevice = new Device(deviceName); + X509Certificate2 authCertificate = null; + if (type == TestDeviceType.X509) { requestDevice.Authentication = new AuthenticationMechanism @@ -88,7 +93,10 @@ private static async Task CreateDeviceAsync(TestDeviceType type, str } }; - auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); +#pragma warning disable CA2000 // Dispose objects before losing scope - X509Certificate and DeviceAuthenticationWithX509Certificate are disposed when TestDevice is disposed. + authCertificate = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + auth = new DeviceAuthenticationWithX509Certificate(deviceName, authCertificate); +#pragma warning restore CA2000 // Dispose objects before losing scope - X509Certificate and DeviceAuthenticationWithX509Certificate are disposed when TestDevice is disposed. } Device device = null; @@ -108,7 +116,10 @@ await RetryOperationHelper return device == null ? throw new Exception($"Exhausted attempts for creating device {device.Id}, requests got throttled.") - : new TestDevice(device, auth); + : new TestDevice(device, auth) + { + _authCertificate = authCertificate, + }; } /// @@ -189,5 +200,25 @@ public async Task RemoveDeviceAsync() using var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await rm.RemoveDeviceAsync(Id).ConfigureAwait(false); } + + public void Dispose() + { + // X509Certificate needs to be disposed for implementations !NET451 (NET451 doesn't implement X509Certificates as IDisposable). + + // Normally we wouldn't be disposing the X509 Certificates here, but rather delegate that to whoever was creating the TestDevice. + // For the design that our test suite follows, it is ok to dispose the X509 certificate here since it won't be referenced by anyone else + // within the scope of the test using this TestDevice. + if (_authCertificate is IDisposable disposableCert) + { + disposableCert?.Dispose(); + } + _authCertificate = null; + + if (AuthenticationMethod is DeviceAuthenticationWithX509Certificate x509Auth) + { + x509Auth?.Dispose(); + } + AuthenticationMethod = null; + } } } diff --git a/e2e/test/helpers/TestDeviceCallbackHandler.cs b/e2e/test/helpers/TestDeviceCallbackHandler.cs index e2b075f91b..3392bedac6 100644 --- a/e2e/test/helpers/TestDeviceCallbackHandler.cs +++ b/e2e/test/helpers/TestDeviceCallbackHandler.cs @@ -23,11 +23,11 @@ public class TestDeviceCallbackHandler : IDisposable private readonly SemaphoreSlim _twinCallbackSemaphore = new SemaphoreSlim(0, 1); private ExceptionDispatchInfo _twinExceptionDispatch; - private string _expectedTwinPropertyValue = null; + private string _expectedTwinPropertyValue; private readonly SemaphoreSlim _receivedMessageCallbackSemaphore = new SemaphoreSlim(0, 1); private ExceptionDispatchInfo _receiveMessageExceptionDispatch; - private Message _expectedMessageSentByService = null; + private Message _expectedMessageSentByService; public TestDeviceCallbackHandler(DeviceClient deviceClient, TestDevice testDevice, MsTestLogger logger) { diff --git a/e2e/test/helpers/TestModule.cs b/e2e/test/helpers/TestModule.cs index 42f8c36ee9..4c1e105a22 100644 --- a/e2e/test/helpers/TestModule.cs +++ b/e2e/test/helpers/TestModule.cs @@ -23,7 +23,7 @@ private TestModule(Module module) /// public static async Task GetTestModuleAsync(string deviceNamePrefix, string moduleNamePrefix, MsTestLogger logger) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, deviceNamePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, deviceNamePrefix).ConfigureAwait(false); string deviceName = testDevice.Id; string moduleName = "E2E_" + moduleNamePrefix + Guid.NewGuid(); diff --git a/e2e/test/helpers/logging/LoggedTestMethod.cs b/e2e/test/helpers/logging/LoggedTestMethod.cs index 8f3cba3b3c..d23438164f 100644 --- a/e2e/test/helpers/logging/LoggedTestMethod.cs +++ b/e2e/test/helpers/logging/LoggedTestMethod.cs @@ -19,7 +19,7 @@ public class LoggedTestMethod : TestMethodAttribute public override TestResult[] Execute(ITestMethod testMethod) { TestResult[] results = base.Execute(testMethod); - var testFailureReason = results.First().TestFailureException?.Message; + string testFailureReason = results.First().TestFailureException?.Message; // Log only if there is an exception in the test run. if (!string.IsNullOrWhiteSpace(testFailureReason)) diff --git a/e2e/test/helpers/logging/TestLogger.cs b/e2e/test/helpers/logging/TestLogger.cs index e447c7f7d8..81f061d53b 100644 --- a/e2e/test/helpers/logging/TestLogger.cs +++ b/e2e/test/helpers/logging/TestLogger.cs @@ -23,9 +23,9 @@ public class TestLogger public const string Service = "IotHub"; // Client to log to application insights. - private TelemetryClient _telemetryClient; + private readonly TelemetryClient _telemetryClient; - private IDictionary _commonProperties; + private readonly IDictionary _commonProperties; private TestLogger() { @@ -35,7 +35,7 @@ private TestLogger() string intrumentationKey = Environment.GetEnvironmentVariable("E2E_IKEY"); if (!string.IsNullOrWhiteSpace(intrumentationKey)) { - var config = new TelemetryConfiguration + using var config = new TelemetryConfiguration { InstrumentationKey = intrumentationKey, }; diff --git a/e2e/test/helpers/templates/FaultInjection.cs b/e2e/test/helpers/templates/FaultInjection.cs index 43436c15fc..ea48313a9e 100644 --- a/e2e/test/helpers/templates/FaultInjection.cs +++ b/e2e/test/helpers/templates/FaultInjection.cs @@ -93,14 +93,13 @@ public static async Task ActivateFaultInjectionAsync(Client.TransportType transp deviceClient.OperationTimeoutInMilliseconds = (uint)delayInSec.TotalMilliseconds; } - await deviceClient - .SendEventAsync( - ComposeErrorInjectionProperties( - faultType, - reason, - delayInSec, - durationInSec)) - .ConfigureAwait(false); + using Client.Message faultInjectionMessage = ComposeErrorInjectionProperties( + faultType, + reason, + delayInSec, + durationInSec); + + await deviceClient.SendEventAsync(faultInjectionMessage).ConfigureAwait(false); } catch (IotHubCommunicationException ex) { @@ -144,7 +143,7 @@ public static async Task TestErrorInjectionAsync( Func cleanupOperation, MsTestLogger logger) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, devicePrefix, type).ConfigureAwait(false); ITransportSettings transportSettings = CreateTransportSettingsFromName(transport, proxyAddress); DeviceClient deviceClient = testDevice.CreateDeviceClient(new ITransportSettings[] { transportSettings }); diff --git a/e2e/test/helpers/templates/FaultInjectionPoolingOverAmqp.cs b/e2e/test/helpers/templates/FaultInjectionPoolingOverAmqp.cs index 2691508e85..0d79c783a3 100644 --- a/e2e/test/helpers/templates/FaultInjectionPoolingOverAmqp.cs +++ b/e2e/test/helpers/templates/FaultInjectionPoolingOverAmqp.cs @@ -26,7 +26,7 @@ public static async Task TestFaultInjectionPoolAmqpAsync( TimeSpan durationInSec, Func initOperation, Func testOperation, - Func, Task> cleanupOperation, + Func, List, Task> cleanupOperation, ConnectionStringAuthScope authScope, MsTestLogger logger) { @@ -43,11 +43,11 @@ public static async Task TestFaultInjectionPoolAmqpAsync( } }; - IList testDevices = new List(); - IList deviceClients = new List(); - IList testDeviceCallbackHandlers = new List(); - IList amqpConnectionStatuses = new List(); - IList operations = new List(); + var testDevices = new List(); + var deviceClients = new List(); + var testDeviceCallbackHandlers = new List(); + var amqpConnectionStatuses = new List(); + var operations = new List(); // Arrange // Initialize the test device client instances @@ -94,7 +94,7 @@ public static async Task TestFaultInjectionPoolAmqpAsync( watch.Start(); logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: {testDevices[0].Id} Requesting fault injection type={faultType} reason={reason}, delay={delayInSec}s, duration={durationInSec}s"); - var faultInjectionMessage = FaultInjection.ComposeErrorInjectionProperties(faultType, reason, delayInSec, durationInSec); + using Client.Message faultInjectionMessage = FaultInjection.ComposeErrorInjectionProperties(faultType, reason, delayInSec, durationInSec); await deviceClients[0].SendEventAsync(faultInjectionMessage).ConfigureAwait(false); logger.Trace($"{nameof(FaultInjection)}: Waiting for fault injection to be active: {delayInSec} seconds."); @@ -205,7 +205,7 @@ public static async Task TestFaultInjectionPoolAmqpAsync( finally { // Close the service-side components and dispose the device client instances. - await cleanupOperation(deviceClients).ConfigureAwait(false); + await cleanupOperation(deviceClients, testDeviceCallbackHandlers).ConfigureAwait(false); watch.Stop(); diff --git a/e2e/test/helpers/templates/PoolingOverAmqp.cs b/e2e/test/helpers/templates/PoolingOverAmqp.cs index 070e52eec8..e6b281f5cb 100644 --- a/e2e/test/helpers/templates/PoolingOverAmqp.cs +++ b/e2e/test/helpers/templates/PoolingOverAmqp.cs @@ -47,11 +47,11 @@ public static async Task TestPoolAmqpAsync( int currentSuccessRate = 0; bool reRunTest = false; - IList testDevices = new List(); - IList deviceClients = new List(); - IList testDeviceCallbackHandlers = new List(); - IList amqpConnectionStatuses = new List(); - IList operations = new List(); + var testDevices = new List(); + var deviceClients = new List(); + var testDeviceCallbackHandlers = new List(); + var amqpConnectionStatuses = new List(); + var operations = new List(); do { @@ -135,10 +135,8 @@ public static async Task TestPoolAmqpAsync( await cleanupOperation().ConfigureAwait(false); } - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); + testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); // Clean up the local lists testDevices.Clear(); diff --git a/e2e/test/iothub/AuthenticationWithTokenRefreshDisposalTests.cs b/e2e/test/iothub/AuthenticationWithTokenRefreshDisposalTests.cs index 4ace5094f6..a594c29e9d 100644 --- a/e2e/test/iothub/AuthenticationWithTokenRefreshDisposalTests.cs +++ b/e2e/test/iothub/AuthenticationWithTokenRefreshDisposalTests.cs @@ -120,6 +120,9 @@ private async Task AuthenticationMethodDisposesTokenRefresher(Client.TransportTy Func act = async () => await deviceClient2.SendEventAsync(message2).ConfigureAwait(false); await act.Should().ThrowAsync(); + + authenticationMethod.Dispose(); + deviceClient2.Dispose(); } private async Task ReuseAuthenticationMethod_SingleDevice(Client.TransportType transport) diff --git a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs index 63e894cb6f..92e1cca9ae 100644 --- a/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs +++ b/e2e/test/iothub/ConnectionStatusChangeHandlerTests.cs @@ -106,7 +106,7 @@ await this.ModuleClient_Gives_ConnectionStatus_DeviceDisabled_Base( private async Task DeviceClient_Gives_ConnectionStatus_DeviceDisabled_Base( Client.TransportType protocol, Func registryManagerOperation) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix + $"_{Guid.NewGuid()}").ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix + $"_{Guid.NewGuid()}").ConfigureAwait(false); string deviceConnectionString = testDevice.ConnectionString; var config = new TestConfiguration.IoTHub.ConnectionStringParser(deviceConnectionString); diff --git a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs index 2698350976..9f367eded3 100644 --- a/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs +++ b/e2e/test/iothub/DeviceClientX509AuthenticationE2ETests.cs @@ -24,6 +24,8 @@ namespace Microsoft.Azure.Devices.E2ETests public class DeviceClientX509AuthenticationE2ETests : E2EMsTestBase { private static readonly string s_devicePrefix = $"{nameof(DeviceClientX509AuthenticationE2ETests)}_"; + private static X509Certificate2 s_selfSignedCertificateWithPrivateKey = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + private static X509Certificate2 s_chainCertificateWithPrivateKey = TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(); private readonly string _hostName; public DeviceClientX509AuthenticationE2ETests() @@ -147,15 +149,17 @@ public async Task X509_Enable_CertificateRevocationCheck_Amqp_WebSocket() public async Task X509_Cert_Chain_Install_Test_MQTT_TCP() { // arrange - var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); - var auth = new DeviceAuthenticationWithX509Certificate( + var chainCerts = new X509Certificate2Collection + { + TestConfiguration.IoTHub.GetRootCACertificate(), + TestConfiguration.IoTHub.GetIntermediate1Certificate(), + TestConfiguration.IoTHub.GetIntermediate2Certificate() + }; + using var auth = new DeviceAuthenticationWithX509Certificate( TestConfiguration.IoTHub.X509ChainDeviceName, - TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + s_chainCertificateWithPrivateKey, chainCerts); - using var deviceClient = DeviceClient.Create( + using DeviceClient deviceClient = DeviceClient.Create( _hostName, auth, DeviceTransportType.Mqtt_Tcp_Only); @@ -172,15 +176,17 @@ public async Task X509_Cert_Chain_Install_Test_MQTT_TCP() public async Task X509_Cert_Chain_Install_Test_AMQP_TCP() { // arrange - var chainCerts = new X509Certificate2Collection(); - chainCerts.Add(TestConfiguration.IoTHub.GetRootCACertificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate1Certificate()); - chainCerts.Add(TestConfiguration.IoTHub.GetIntermediate2Certificate()); - var auth = new DeviceAuthenticationWithX509Certificate( + var chainCerts = new X509Certificate2Collection + { + TestConfiguration.IoTHub.GetRootCACertificate(), + TestConfiguration.IoTHub.GetIntermediate1Certificate(), + TestConfiguration.IoTHub.GetIntermediate2Certificate() + }; + using var auth = new DeviceAuthenticationWithX509Certificate( TestConfiguration.IoTHub.X509ChainDeviceName, - TestConfiguration.IoTHub.GetChainDeviceCertificateWithPrivateKey(), + s_chainCertificateWithPrivateKey, chainCerts); - using var deviceClient = DeviceClient.Create( + using DeviceClient deviceClient = DeviceClient.Create( _hostName, auth, DeviceTransportType.Amqp_Tcp_Only); @@ -217,14 +223,12 @@ private void ValidateCertsAreInstalled(X509Certificate2Collection certificates) private async Task SendMessageTest(ITransportSettings transportSetting) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, TestDeviceType.X509).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, TestDeviceType.X509).ConfigureAwait(false); - using (DeviceClient deviceClient = testDevice.CreateDeviceClient(new[] { transportSetting })) - { - await deviceClient.OpenAsync().ConfigureAwait(false); - await MessageSendE2ETests.SendSingleMessageAsync(deviceClient, testDevice.Id, Logger).ConfigureAwait(false); - await deviceClient.CloseAsync().ConfigureAwait(false); - } + using DeviceClient deviceClient = testDevice.CreateDeviceClient(new[] { transportSetting }); + await deviceClient.OpenAsync().ConfigureAwait(false); + await MessageSendE2ETests.SendSingleMessageAsync(deviceClient, testDevice.Id, Logger).ConfigureAwait(false); + await deviceClient.CloseAsync().ConfigureAwait(false); } private ITransportSettings CreateHttpTransportSettingWithCertificateRevocationCheck() @@ -247,8 +251,32 @@ private ITransportSettings CreateAmqpTransportSettingWithCertificateRevocationCh private async Task X509InvalidDeviceIdOpenAsyncTest(Client.TransportType transportType) { - var deviceClient = CreateDeviceClientWithInvalidId(transportType); - using (deviceClient) + string deviceName = $"DEVICE_NOT_EXIST_{Guid.NewGuid()}"; + using var auth = new DeviceAuthenticationWithX509Certificate(deviceName, s_selfSignedCertificateWithPrivateKey); + using DeviceClient deviceClient = DeviceClient.Create(_hostName, auth, transportType); + + try + { + await deviceClient.OpenAsync().ConfigureAwait(false); + Assert.Fail("Should throw UnauthorizedException but didn't."); + } + catch (UnauthorizedException) + { + // It should always throw UnauthorizedException + } + + // Check TCP connection to verify there is no connection leak + // netstat -na | find "[Your Hub IP]" | find "ESTABLISHED" + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false); + } + + private async Task X509InvalidDeviceIdOpenAsyncTwiceTest(Client.TransportType transportType) + { + string deviceName = $"DEVICE_NOT_EXIST_{Guid.NewGuid()}"; + using var auth = new DeviceAuthenticationWithX509Certificate(deviceName, s_selfSignedCertificateWithPrivateKey); + using DeviceClient deviceClient = DeviceClient.Create(_hostName, auth, transportType); + + for (int i = 0; i < 2; i++) { try { @@ -259,42 +287,30 @@ private async Task X509InvalidDeviceIdOpenAsyncTest(Client.TransportType transpo { // It should always throw UnauthorizedException } - - // Check TCP connection to verify there is no connection leak - // netstat -na | find "[Your Hub IP]" | find "ESTABLISHED" - await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false); } + + // Check TCP connection to verify there is no connection leak + // netstat -na | find "[Your Hub IP]" | find "ESTABLISHED" + await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false); } - private async Task X509InvalidDeviceIdOpenAsyncTwiceTest(Client.TransportType transportType) + [ClassCleanup] + public static void ClassCleanup() { - var deviceClient = CreateDeviceClientWithInvalidId(transportType); - using (deviceClient) + // X509Certificate needs to be disposed for implementations !NET451 (NET451 doesn't implement X509Certificates as IDisposable). + if (s_selfSignedCertificateWithPrivateKey is IDisposable disposableSelfSignedCertificate) { - for (int i = 0; i < 2; i++) - { - try - { - await deviceClient.OpenAsync().ConfigureAwait(false); - Assert.Fail("Should throw UnauthorizedException but didn't."); - } - catch (UnauthorizedException) - { - // It should always throw UnauthorizedException - } - } - - // Check TCP connection to verify there is no connection leak - // netstat -na | find "[Your Hub IP]" | find "ESTABLISHED" - await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false); + disposableSelfSignedCertificate?.Dispose(); } - } + s_selfSignedCertificateWithPrivateKey = null; - private DeviceClient CreateDeviceClientWithInvalidId(Client.TransportType transportType) - { - string deviceName = $"DEVICE_NOT_EXIST_{Guid.NewGuid()}"; - var auth = new DeviceAuthenticationWithX509Certificate(deviceName, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); - return DeviceClient.Create(_hostName, auth, transportType); + // X509Certificate needs to be disposed for implementations !NET451 (NET451 doesn't implement X509Certificates as IDisposable). + if (s_chainCertificateWithPrivateKey is IDisposable disposableChainedCertificate) + { + disposableChainedCertificate?.Dispose(); + + } + s_chainCertificateWithPrivateKey = null; } } } diff --git a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs index 516d8ed6d9..96f308eacc 100644 --- a/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs +++ b/e2e/test/iothub/DeviceTokenRefreshE2ETests.cs @@ -28,7 +28,7 @@ public class DeviceTokenRefreshE2ETests : E2EMsTestBase [ExpectedException(typeof(DeviceNotFoundException))] public async Task DeviceClient_Not_Exist_AMQP() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString($"HostName={config.IotHubHostName};DeviceId=device_id_not_exist;SharedAccessKey={config.SharedAccessKey}", Client.TransportType.Amqp_Tcp_Only)) @@ -41,7 +41,7 @@ public async Task DeviceClient_Not_Exist_AMQP() [ExpectedException(typeof(UnauthorizedException))] public async Task DeviceClient_Bad_Credentials_AMQP() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); var config = new TestConfiguration.IoTHub.ConnectionStringParser(testDevice.ConnectionString); string invalidKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("invalid_key")); @@ -78,7 +78,7 @@ public async Task DeviceClient_TokenIsRefreshed_Ok_Mqtt() [LoggedTestMethod] public async Task DeviceClient_TokenConnectionDoubleRelease_Ok() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); string deviceConnectionString = testDevice.ConnectionString; @@ -87,29 +87,30 @@ public async Task DeviceClient_TokenConnectionDoubleRelease_Ok() string deviceId = config.DeviceID; string key = config.SharedAccessKey; - SharedAccessSignatureBuilder builder = new SharedAccessSignatureBuilder() + var builder = new SharedAccessSignatureBuilder() { Key = key, TimeToLive = new TimeSpan(0, 10, 0), Target = $"{iotHub}/devices/{WebUtility.UrlEncode(deviceId)}", }; - DeviceAuthenticationWithToken auth = new DeviceAuthenticationWithToken(deviceId, builder.ToSignature()); + var auth = new DeviceAuthenticationWithToken(deviceId, builder.ToSignature()); - using (DeviceClient deviceClient = DeviceClient.Create(iotHub, auth, Client.TransportType.Amqp_Tcp_Only)) - { - Logger.Trace($"{deviceId}: Created {nameof(DeviceClient)} ID={TestLogger.IdOf(deviceClient)}"); + using DeviceClient deviceClient = DeviceClient.Create(iotHub, auth, Client.TransportType.Amqp_Tcp_Only); + Logger.Trace($"{deviceId}: Created {nameof(DeviceClient)} ID={TestLogger.IdOf(deviceClient)}"); - Logger.Trace($"{deviceId}: DeviceClient OpenAsync."); - await deviceClient.OpenAsync().ConfigureAwait(false); - Logger.Trace($"{deviceId}: DeviceClient SendEventAsync."); - await deviceClient.SendEventAsync(new Client.Message(Encoding.UTF8.GetBytes("TestMessage"))).ConfigureAwait(false); - Logger.Trace($"{deviceId}: DeviceClient CloseAsync."); - await deviceClient.CloseAsync().ConfigureAwait(false); // First release - } // Second release + Logger.Trace($"{deviceId}: DeviceClient OpenAsync."); + await deviceClient.OpenAsync().ConfigureAwait(false); + + Logger.Trace($"{deviceId}: DeviceClient SendEventAsync."); + using var testMessage = new Client.Message(Encoding.UTF8.GetBytes("TestMessage")); + await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false); + + Logger.Trace($"{deviceId}: DeviceClient CloseAsync."); + await deviceClient.CloseAsync().ConfigureAwait(false); } - // The easiest way to test that sas tokens expire with custom expiration time via the CreateFromConnectionString flow is + // The easiest way to test that sas tokens expire with custom expiration time via the CreateFromConnectionString flow is // by initializing a DeviceClient instance over Mqtt (since sas token expiration over Mqtt is accompanied by a disconnect). [LoggedTestMethod] [TestCategory("LongRunning")] @@ -145,7 +146,7 @@ public async Task DeviceClient_CreateFromConnectionString_TokenIsRefreshed_Mqtt( }); deviceClient.OperationTimeoutInMilliseconds = (uint)operationTimeoutInMilliseconds; - var message = new Client.Message(Encoding.UTF8.GetBytes("Hello")); + using var message = new Client.Message(Encoding.UTF8.GetBytes("Hello")); Logger.Trace($"[{testDevice.Id}]: SendEventAsync (1)"); await deviceClient.SendEventAsync(message).ConfigureAwait(false); @@ -168,13 +169,13 @@ public async Task DeviceClient_CreateFromConnectionString_TokenIsRefreshed_Mqtt( private async Task DeviceClient_TokenIsRefreshed_Internal(Client.TransportType transport, int ttl = 20) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); int buffer = 50; Device device = testDevice.Device; - SemaphoreSlim deviceDisconnected = new SemaphoreSlim(0); + using var deviceDisconnected = new SemaphoreSlim(0); - var refresher = new TestTokenRefresher( + using var refresher = new TestTokenRefresher( device.Id, device.Authentication.SymmetricKey.PrimaryKey, ttl, @@ -182,77 +183,73 @@ private async Task DeviceClient_TokenIsRefreshed_Internal(Client.TransportType t transport, Logger); - using (DeviceClient deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, refresher, transport)) - { - Logger.Trace($"Created {nameof(DeviceClient)} ID={TestLogger.IdOf(deviceClient)}"); + using DeviceClient deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, refresher, transport); + Logger.Trace($"Created {nameof(DeviceClient)} ID={TestLogger.IdOf(deviceClient)}"); - if (transport == Client.TransportType.Mqtt) + if (transport == Client.TransportType.Mqtt) + { + deviceClient.SetConnectionStatusChangesHandler((ConnectionStatus status, ConnectionStatusChangeReason reason) => { - deviceClient.SetConnectionStatusChangesHandler((ConnectionStatus status, ConnectionStatusChangeReason reason) => + Logger.Trace($"{nameof(ConnectionStatusChangesHandler)}: {status}; {reason}"); + if (status == ConnectionStatus.Disconnected_Retrying || status == ConnectionStatus.Disconnected) { - Logger.Trace($"{nameof(ConnectionStatusChangesHandler)}: {status}; {reason}"); - if (status == ConnectionStatus.Disconnected_Retrying || status == ConnectionStatus.Disconnected) - { - deviceDisconnected.Release(); - } - }); - } + deviceDisconnected.Release(); + } + }); + } - var message = new Client.Message(Encoding.UTF8.GetBytes("Hello")); + using var message = new Client.Message(Encoding.UTF8.GetBytes("Hello")); - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(ttl * 10))) - { - try - { - // Create the first Token. - Logger.Trace($"[{DateTime.UtcNow}] OpenAsync"); - await deviceClient.OpenAsync(cts.Token).ConfigureAwait(false); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(ttl * 10)); + try + { + // Create the first Token. + Logger.Trace($"[{DateTime.UtcNow}] OpenAsync"); + await deviceClient.OpenAsync(cts.Token).ConfigureAwait(false); - Logger.Trace($"[{DateTime.UtcNow}] SendEventAsync (1)"); - await deviceClient.SendEventAsync(message, cts.Token).ConfigureAwait(false); - await refresher.WaitForTokenRefreshAsync(cts.Token).ConfigureAwait(false); - } - catch (OperationCanceledException ex) - { - Assert.Fail($"{TestLogger.IdOf(deviceClient)} did not get the initial token. {ex}"); - throw; - } + Logger.Trace($"[{DateTime.UtcNow}] SendEventAsync (1)"); + await deviceClient.SendEventAsync(message, cts.Token).ConfigureAwait(false); + await refresher.WaitForTokenRefreshAsync(cts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException ex) + { + Assert.Fail($"{TestLogger.IdOf(deviceClient)} did not get the initial token. {ex}"); + throw; + } - // Wait for the Token to expire. - if (transport == Client.TransportType.Http1) - { - float waitTime = (float)ttl * ((float)buffer / 100) + 1; - Logger.Trace($"[{DateTime.UtcNow}] Waiting {waitTime} seconds."); - await Task.Delay(TimeSpan.FromSeconds(waitTime)).ConfigureAwait(false); - } - else if (transport == Client.TransportType.Mqtt) - { - Logger.Trace($"[{DateTime.UtcNow}] Waiting for device disconnect."); - await deviceDisconnected.WaitAsync(cts.Token).ConfigureAwait(false); - } + // Wait for the Token to expire. + if (transport == Client.TransportType.Http1) + { + float waitTime = (float)ttl * ((float)buffer / 100) + 1; + Logger.Trace($"[{DateTime.UtcNow}] Waiting {waitTime} seconds."); + await Task.Delay(TimeSpan.FromSeconds(waitTime)).ConfigureAwait(false); + } + else if (transport == Client.TransportType.Mqtt) + { + Logger.Trace($"[{DateTime.UtcNow}] Waiting for device disconnect."); + await deviceDisconnected.WaitAsync(cts.Token).ConfigureAwait(false); + } - try - { - Logger.Trace($"[{DateTime.UtcNow}] SendEventAsync (2)"); - await deviceClient.SendEventAsync(message, cts.Token).ConfigureAwait(false); - await refresher.WaitForTokenRefreshAsync(cts.Token).ConfigureAwait(false); - } - catch (OperationCanceledException ex) - { - Assert.Fail($"{TestLogger.IdOf(deviceClient)} did not refresh token after {refresher.DetectedRefreshInterval}. {ex}"); - throw; - } + try + { + Logger.Trace($"[{DateTime.UtcNow}] SendEventAsync (2)"); + await deviceClient.SendEventAsync(message, cts.Token).ConfigureAwait(false); + await refresher.WaitForTokenRefreshAsync(cts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException ex) + { + Assert.Fail($"{TestLogger.IdOf(deviceClient)} did not refresh token after {refresher.DetectedRefreshInterval}. {ex}"); + throw; + } - // Ensure that the token was refreshed. - Logger.Trace($"[{DateTime.UtcNow}] Token was refreshed after {refresher.DetectedRefreshInterval} (ttl = {ttl} seconds)."); - Assert.IsTrue( - refresher.DetectedRefreshInterval.TotalSeconds < (float)ttl * (1 + (float)buffer / 100), // Wait for more than what we expect. - $"Token was refreshed after {refresher.DetectedRefreshInterval} although ttl={ttl} seconds."); + // Ensure that the token was refreshed. + Logger.Trace($"[{DateTime.UtcNow}] Token was refreshed after {refresher.DetectedRefreshInterval} (ttl = {ttl} seconds)."); + Assert.IsTrue( + refresher.DetectedRefreshInterval.TotalSeconds < (float)ttl * (1 + (float)buffer / 100), // Wait for more than what we expect. + $"Token was refreshed after {refresher.DetectedRefreshInterval} although ttl={ttl} seconds."); - Logger.Trace($"[{DateTime.UtcNow}] CloseAsync"); - await deviceClient.CloseAsync().ConfigureAwait(false); - } - } + Logger.Trace($"[{DateTime.UtcNow}] CloseAsync"); + await deviceClient.CloseAsync().ConfigureAwait(false); } private class TestTokenRefresher : DeviceAuthenticationWithTokenRefresh diff --git a/e2e/test/iothub/FileUploadE2ETests.cs b/e2e/test/iothub/FileUploadE2ETests.cs index 3a0309a525..d461b0788b 100644 --- a/e2e/test/iothub/FileUploadE2ETests.cs +++ b/e2e/test/iothub/FileUploadE2ETests.cs @@ -20,9 +20,10 @@ namespace Microsoft.Azure.Devices.E2ETests [TestCategory("IoTHub")] public class FileUploadE2ETests : E2EMsTestBase { - private readonly string _devicePrefix = $"{nameof(FileUploadE2ETests)}_"; private const int FileSizeSmall = 10 * 1024; private const int FileSizeBig = 5120 * 1024; + private readonly string _devicePrefix = $"{nameof(FileUploadE2ETests)}_"; + private static readonly X509Certificate2 s_selfSignedCertificate = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); [LoggedTestMethod] [TestCategory("LongRunning")] @@ -92,7 +93,7 @@ public async Task FileUpload_SmallFile_Http_GranularSteps_x509() using var fileStreamSource = new FileStream(filename, FileMode.Open, FileAccess.Read); var fileUploadTransportSettings = new Http1TransportSettings(); - await UploadFileGranularAsync(fileStreamSource, filename, fileUploadTransportSettings, x509auth: true).ConfigureAwait(false); + await UploadFileGranularAsync(fileStreamSource, filename, fileUploadTransportSettings, useX509auth: true).ConfigureAwait(false); } [LoggedTestMethod] @@ -109,12 +110,12 @@ public async Task FileUpload_SmallFile_Http_GranularSteps_Proxy() await UploadFileGranularAsync(fileStreamSource, filename, fileUploadTransportSettings).ConfigureAwait(false); } - private async Task UploadFileGranularAsync(Stream source, string filename, Http1TransportSettings fileUploadTransportSettings, bool x509auth = false) + private async Task UploadFileGranularAsync(Stream source, string filename, Http1TransportSettings fileUploadTransportSettings, bool useX509auth = false) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync( + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync( Logger, _devicePrefix, - x509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false); + useX509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false); DeviceClient deviceClient; var clientOptions = new ClientOptions() @@ -122,12 +123,14 @@ private async Task UploadFileGranularAsync(Stream source, string filename, Http1 FileUploadTransportSettings = fileUploadTransportSettings }; - if (x509auth) + X509Certificate2 cert = null; + DeviceAuthenticationWithX509Certificate x509Auth = null; + if (useX509auth) { - X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); - - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); - deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, Client.TransportType.Http1); + cert = s_selfSignedCertificate; + x509Auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); + + deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, x509Auth, Client.TransportType.Http1); } else { @@ -139,36 +142,43 @@ private async Task UploadFileGranularAsync(Stream source, string filename, Http1 BlobName = filename }; - FileUploadSasUriResponse fileUploadSasUriResponse = await deviceClient.GetFileUploadSasUriAsync(fileUploadSasUriRequest).ConfigureAwait(false); + using (deviceClient) + { + FileUploadSasUriResponse fileUploadSasUriResponse = await deviceClient.GetFileUploadSasUriAsync(fileUploadSasUriRequest).ConfigureAwait(false); - var blob = new CloudBlockBlob(fileUploadSasUriResponse.GetBlobUri()); - Task uploadTask = blob.UploadFromStreamAsync(source); - await uploadTask.ConfigureAwait(false); + var blob = new CloudBlockBlob(fileUploadSasUriResponse.GetBlobUri()); + Task uploadTask = blob.UploadFromStreamAsync(source); + await uploadTask.ConfigureAwait(false); - var notification = new FileUploadCompletionNotification - { - CorrelationId = fileUploadSasUriResponse.CorrelationId, - IsSuccess = uploadTask.IsCompleted - }; + var notification = new FileUploadCompletionNotification + { + CorrelationId = fileUploadSasUriResponse.CorrelationId, + IsSuccess = uploadTask.IsCompleted + }; + + await deviceClient.CompleteFileUploadAsync(notification).ConfigureAwait(false); + } - await deviceClient.CompleteFileUploadAsync(notification).ConfigureAwait(false); + x509Auth?.Dispose(); } [Obsolete] - private async Task UploadFileAsync(Client.TransportType transport, string filename, bool x509auth = false) + private async Task UploadFileAsync(Client.TransportType transport, string filename, bool useX509auth = false) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync( + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync( Logger, _devicePrefix, - x509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false); + useX509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false); DeviceClient deviceClient; - if (x509auth) + X509Certificate2 cert = null; + DeviceAuthenticationWithX509Certificate x509Auth = null; + if (useX509auth) { - X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + cert = s_selfSignedCertificate; + x509Auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); - deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); + deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, x509Auth, transport); } else { @@ -177,30 +187,33 @@ private async Task UploadFileAsync(Client.TransportType transport, string filena using (deviceClient) { - using (var fileStreamSource = new FileStream(filename, FileMode.Open, FileAccess.Read)) - { - // UploadToBlobAsync is obsolete, added [Obsolete] attribute to suppress CS0618 message - await deviceClient.UploadToBlobAsync(filename, fileStreamSource).ConfigureAwait(false); - } + using var fileStreamSource = new FileStream(filename, FileMode.Open, FileAccess.Read); + + // UploadToBlobAsync is obsolete, added [Obsolete] attribute to suppress CS0618 message + await deviceClient.UploadToBlobAsync(filename, fileStreamSource).ConfigureAwait(false); await deviceClient.CloseAsync().ConfigureAwait(false); } + + x509Auth?.Dispose(); } - private async Task GetSasUriAsync(Client.TransportType transport, string blobName, bool x509auth = false) + private async Task GetSasUriAsync(Client.TransportType transport, string blobName, bool useX509auth = false) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync( + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync( Logger, _devicePrefix, - x509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false); + useX509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false); DeviceClient deviceClient; - if (x509auth) + X509Certificate2 cert = null; + DeviceAuthenticationWithX509Certificate x509Auth = null; + if (useX509auth) { - X509Certificate2 cert = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + cert = s_selfSignedCertificate; + x509Auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, cert); - deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, auth, transport); + deviceClient = DeviceClient.Create(testDevice.IoTHubHostName, x509Auth, transport); } else { @@ -212,6 +225,8 @@ private async Task GetSasUriAsync(Client.TransportType transport, string blobNam FileUploadSasUriResponse sasUriResponse = await deviceClient.GetFileUploadSasUriAsync(new FileUploadSasUriRequest { BlobName = blobName }); await deviceClient.CloseAsync().ConfigureAwait(false); } + + x509Auth?.Dispose(); } private static async Task GetTestFileNameAsync(int fileSize) @@ -231,5 +246,15 @@ private static async Task GetTestFileNameAsync(int fileSize) return filePath; } + + [ClassCleanup] + public static void CleanupCertificates() + { + // X509Certificate needs to be disposed for implementations !NET451 (NET451 doesn't implement X509Certificates as IDisposable). + if (s_selfSignedCertificate is IDisposable disposableCertificate) + { + disposableCertificate?.Dispose(); + } + } } } diff --git a/e2e/test/iothub/FileUploadFaultInjectionTests.cs b/e2e/test/iothub/FileUploadFaultInjectionTests.cs index a729c445e1..8e261f4312 100644 --- a/e2e/test/iothub/FileUploadFaultInjectionTests.cs +++ b/e2e/test/iothub/FileUploadFaultInjectionTests.cs @@ -85,9 +85,9 @@ private async Task UploadFileDisconnectTransport( TimeSpan durationInSec = default, TimeSpan retryDurationInMilliSec = default) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); TimeSpan operationTimeout = retryDurationInMilliSec == TimeSpan.Zero ? FaultInjection.RecoveryTime : retryDurationInMilliSec; deviceClient.OperationTimeoutInMilliseconds = (uint)operationTimeout.TotalMilliseconds; @@ -119,7 +119,8 @@ private static async Task SendErrorInjectionMessageAsync( { try { - await deviceClient.SendEventAsync(FaultInjection.ComposeErrorInjectionProperties(faultType, reason, delayInSec, durationInSec)).ConfigureAwait(false); + using Client.Message faultInjectionMessage = FaultInjection.ComposeErrorInjectionProperties(faultType, reason, delayInSec, durationInSec); + await deviceClient.SendEventAsync(faultInjectionMessage).ConfigureAwait(false); } catch { diff --git a/e2e/test/iothub/NoRetryE2ETests.cs b/e2e/test/iothub/NoRetryE2ETests.cs index 9d1e3ba519..f705b701ae 100644 --- a/e2e/test/iothub/NoRetryE2ETests.cs +++ b/e2e/test/iothub/NoRetryE2ETests.cs @@ -24,7 +24,7 @@ public class NoRetryE2ETests : E2EMsTestBase [TestCategory("FaultInjection")] public async Task FaultInjection_NoRetry_NoRecovery_OpenAsync() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, TestDeviceType.Sasl).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, TestDeviceType.Sasl).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Amqp_Tcp_Only); Logger.Trace($"{nameof(FaultInjection_NoRetry_NoRecovery_OpenAsync)}: deviceId={testDevice.Id}"); @@ -83,7 +83,7 @@ await FaultInjection [LoggedTestMethod] public async Task DuplicateDevice_NoRetry_NoPingpong_OpenAsync() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, TestDeviceType.Sasl).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, TestDeviceType.Sasl).ConfigureAwait(false); Logger.Trace($"{nameof(DuplicateDevice_NoRetry_NoPingpong_OpenAsync)}: 2 device client instances with the same deviceId={testDevice.Id}."); diff --git a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs index 32c58b13e0..c2e1581d4c 100644 --- a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs +++ b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTLogAnalyticsClient.cs @@ -49,6 +49,8 @@ public class AzureSecurityCenterForIoTLogAnalyticsClient : IDisposable private readonly AuthenticationContext _authenticationContext; private readonly IClientAssertionCertificate _certificateAssertion; + private readonly X509Certificate2 _authCertificate; + //These are used in NET451 instead of OperationalInsights SDK private readonly HttpClient _client; @@ -65,8 +67,8 @@ private AzureSecurityCenterForIoTLogAnalyticsClient() _queryUri = string.Format(CultureInfo.InvariantCulture, QueryUriTemplate, LogAnalyticsApiVersion, _workspaceId); string authority = string.Format(CultureInfo.InvariantCulture, AuthenticationAuthorityTemplate, _aadTenant); _authenticationContext = new AuthenticationContext(authority); - var cert = new X509Certificate2(Convert.FromBase64String(_appCertificate)); - _certificateAssertion = new ClientAssertionCertificate(_appId, cert); + _authCertificate = new X509Certificate2(Convert.FromBase64String(_appCertificate)); + _certificateAssertion = new ClientAssertionCertificate(_appId, _authCertificate); } public async Task IsRawEventExist(string deviceId, string eventId) @@ -137,6 +139,12 @@ protected virtual void Dispose(bool disposing) if (disposing) { _client?.Dispose(); + + // X509Certificate needs to be disposed for implementations !NET451 (NET451 doesn't implement X509Certificates as IDisposable). + if (_authCertificate is IDisposable disposableCert) + { + disposableCert?.Dispose(); + } } } } diff --git a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTSecurityMessageE2ETests.cs b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTSecurityMessageE2ETests.cs index a6c2aa0282..dbe1105982 100644 --- a/e2e/test/iothub/messaging/AzureSecurityCenterForIoTSecurityMessageE2ETests.cs +++ b/e2e/test/iothub/messaging/AzureSecurityCenterForIoTSecurityMessageE2ETests.cs @@ -125,7 +125,7 @@ private JObject ComposeAzureSecurityCenterForIoTSecurityMessagePayload(string ev private async Task TestSecurityMessageAsync(Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); try @@ -162,7 +162,7 @@ private async Task SendSingleSecurityMessageAsync( { await deviceClient.OpenAsync().ConfigureAwait(false); - Client.Message testMessage = ComposeD2CSecurityTestMessage(out string eventId, out string payload, out string p1Value); + using Client.Message testMessage = ComposeD2CSecurityTestMessage(out string eventId, out string payload, out string p1Value); await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false); await ValidateEventAsync(deviceId, eventId, logAnalticsTestClient).ConfigureAwait(false); @@ -174,7 +174,7 @@ private async Task SendSingleSecurityMessageModuleAsync( AzureSecurityCenterForIoTLogAnalyticsClient logAnalticsTestClient) { await moduleClient.OpenAsync().ConfigureAwait(false); - Client.Message testMessage = ComposeD2CSecurityTestMessage(out string eventId, out _, out _); + using Client.Message testMessage = ComposeD2CSecurityTestMessage(out string eventId, out _, out _); await moduleClient.SendEventAsync(testMessage).ConfigureAwait(false); await ValidateEventAsync(deviceId, eventId, logAnalticsTestClient).ConfigureAwait(false); diff --git a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs index da01f7bc24..e23297a804 100644 --- a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageReceiveFaultInjectionPoolAmqpTests.cs @@ -895,7 +895,7 @@ private async Task ReceiveMessageRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) { @@ -911,16 +911,14 @@ await MessageReceiveE2ETests.VerifyReceivedC2DMessageAsync(transport, deviceClie .ConfigureAwait(false); } - async Task CleanupOperationAsync(IList deviceClients) + async Task CleanupOperationAsync(List deviceClients, List testDeviceCallbackHandlers) { await serviceClient.CloseAsync() .ConfigureAwait(false); serviceClient.Dispose(); - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); + testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); } await FaultInjectionPoolingOverAmqp @@ -955,7 +953,7 @@ private async Task ReceiveMessageUsingCallbackRecoveryPoolOverAmqpAsync( string proxyAddress = null) { // Initialize the service client - var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { @@ -977,15 +975,13 @@ async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, receivedMessage.Should().BeNull(); } - async Task CleanupOperationAsync(IList deviceClients) + async Task CleanupOperationAsync(List deviceClients, List testDeviceCallbackHandlers) { await serviceClient.CloseAsync().ConfigureAwait(false); serviceClient.Dispose(); - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); + testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); } await FaultInjectionPoolingOverAmqp diff --git a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageSendFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageSendFaultInjectionPoolAmqpTests.cs index 58e8801f79..de86d5dd88 100644 --- a/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageSendFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/messaging/FaultInjectionPoolAmqpTests.MessageSendFaultInjectionPoolAmqpTests.cs @@ -892,13 +892,9 @@ async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false); } - Task CleanupOperationAsync(IList deviceClients) + Task CleanupOperationAsync(List deviceClients, List _) { - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } - + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); return Task.FromResult(0); } diff --git a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs index 81f867c9ef..8f0ab42de0 100644 --- a/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageFeedbackE2ETests.cs @@ -31,7 +31,7 @@ public async Task Message_CompleteMixOrder_AMQP() private static async Task CompleteMessageMixOrder(TestDeviceType type, Client.TransportType transport, MsTestLogger logger) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, s_devicePrefix, type).ConfigureAwait(false); using (DeviceClient deviceClient = testDevice.CreateDeviceClient(transport)) using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) { diff --git a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs index 3bd205e88f..e3e58f40f1 100644 --- a/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageReceiveE2ETests.cs @@ -585,7 +585,7 @@ public static async Task VerifyReceivedC2dMessageWithCancellationTokenAsync(Clie private async Task ReceiveMessageInOperationTimeoutAsync(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); Logger.Trace($"{nameof(ReceiveMessageInOperationTimeoutAsync)} - calling OpenAsync() for transport={transport}"); @@ -621,9 +621,9 @@ private async Task ReceiveMessageInOperationTimeoutAsync(TestDeviceType type, Cl private async Task ReceiveSingleMessageAsync(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -665,9 +665,9 @@ private async Task Mqtt_ReceiveSingleMessageWithCancelledAsync(TestDeviceType ty private async Task ReceiveSingleMessageWithCancellationTokenAsync(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); await deviceClient.OpenAsync().ConfigureAwait(false); await serviceClient.OpenAsync().ConfigureAwait(false); @@ -729,11 +729,11 @@ private static async Task ReceiveMessageWithoutTimeoutCheckAsync(DeviceClient dc private async Task ReceiveSingleMessageUsingCallbackAsync(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); using (msg) @@ -756,11 +756,11 @@ await Task private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // For Mqtt - we will need to subscribe to the Mqtt receive telemetry topic // before the device can begin receiving c2d messages. @@ -832,12 +832,12 @@ private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType private async Task ReceiveMessageUsingCallbackUpdateHandlerAsync(TestDeviceType type, Client.TransportType transport) { - var firstHandlerSemaphore = new SemaphoreSlim(0, 1); - var secondHandlerSemaphore = new SemaphoreSlim(0, 1); + using var firstHandlerSemaphore = new SemaphoreSlim(0, 1); + using var secondHandlerSemaphore = new SemaphoreSlim(0, 1); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Set the first C2D message handler. await deviceClient.SetReceiveMessageHandlerAsync( @@ -892,11 +892,11 @@ await Task private async Task ReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); @@ -935,11 +935,11 @@ private async Task ReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType typ private async Task DoNotReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType type, ITransportSettings[] settings) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(settings); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); diff --git a/e2e/test/iothub/messaging/MessageSendE2ETests.cs b/e2e/test/iothub/messaging/MessageSendE2ETests.cs index 7cca652d71..5872b277e7 100644 --- a/e2e/test/iothub/messaging/MessageSendE2ETests.cs +++ b/e2e/test/iothub/messaging/MessageSendE2ETests.cs @@ -222,7 +222,7 @@ public async Task X509_DeviceSendBatchMessages_Http() [ExpectedException(typeof(MessageTooLargeException))] public async Task Message_ClientThrowsForMqttTopicNameTooLong() { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(Client.TransportType.Mqtt); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -343,7 +343,7 @@ public async Task Message_DeviceSendMessageWayOverAllowedSize_Http() private async Task SendSingleMessage(TestDeviceType type, Client.TransportType transport, int messageSize = 0) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -353,7 +353,7 @@ private async Task SendSingleMessage(TestDeviceType type, Client.TransportType t private async Task SendBatchMessages(TestDeviceType type, Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); await deviceClient.OpenAsync().ConfigureAwait(false); @@ -363,7 +363,7 @@ private async Task SendBatchMessages(TestDeviceType type, Client.TransportType t private async Task SendSingleMessage(TestDeviceType type, ITransportSettings[] transportSettings, int messageSize = 0) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings); await deviceClient.OpenAsync().ConfigureAwait(false); diff --git a/e2e/test/iothub/method/FaultInjectionPoolAmqpTests.MethodFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/method/FaultInjectionPoolAmqpTests.MethodFaultInjectionPoolAmqpTests.cs index 28179e035d..3e23e4c797 100644 --- a/e2e/test/iothub/method/FaultInjectionPoolAmqpTests.MethodFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/method/FaultInjectionPoolAmqpTests.MethodFaultInjectionPoolAmqpTests.cs @@ -735,22 +735,16 @@ private async Task SendMethodAndRespondRecoveryPoolOverAmqpAsync( ConnectionStringAuthScope authScope = ConnectionStringAuthScope.Device, string proxyAddress = null) { - var testDevicesWithCallbackHandler = new Dictionary(); - - async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) + async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { - var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - testDevicesWithCallbackHandler.Add(testDevice.Id, testDeviceCallbackHandler); - Logger.Trace($"{nameof(MethodE2EPoolAmqpTests)}: Setting method callback handler for device {testDevice.Id}"); await testDeviceCallbackHandler .SetDeviceReceiveMethodAsync(MethodName, MethodE2ETests.DeviceResponseJson, MethodE2ETests.ServiceRequestJson) .ConfigureAwait(false); } - async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) + async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { - TestDeviceCallbackHandler testDeviceCallbackHandler = testDevicesWithCallbackHandler[testDevice.Id]; using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); Logger.Trace($"{nameof(MethodE2EPoolAmqpTests)}: Preparing to receive method for device {testDevice.Id}"); @@ -766,20 +760,11 @@ async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, await Task.WhenAll(serviceSendTask, methodReceivedTask).ConfigureAwait(false); } - async Task CleanupOperationAsync(IList deviceClients) + async Task CleanupOperationAsync(List deviceClients, List testDeviceCallbackHandlers) { - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } - - foreach (KeyValuePair entry in testDevicesWithCallbackHandler) - { - TestDeviceCallbackHandler testDeviceCallbackHandler = entry.Value; - testDeviceCallbackHandler?.Dispose(); - } - - testDevicesWithCallbackHandler.Clear(); + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); + testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); + await Task.FromResult(false).ConfigureAwait(false); } diff --git a/e2e/test/iothub/method/MethodE2ETests.cs b/e2e/test/iothub/method/MethodE2ETests.cs index d4b71ec71f..973b2dcfbb 100644 --- a/e2e/test/iothub/method/MethodE2ETests.cs +++ b/e2e/test/iothub/method/MethodE2ETests.cs @@ -159,7 +159,7 @@ await SendMethodAndRespondAsync( public async Task Method_ServiceInvokeDeviceMethodWithUnknownDeviceThrows() { // setup - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -232,8 +232,8 @@ public async Task Method_ModuleReceivesMethodAndResponseWithDefaultMethodHandler public async Task Method_ServiceInvokeDeviceMethodWithUnknownModuleThrows() { // setup - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, "ModuleNotFoundTest").ConfigureAwait(false); - using var serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, "ModuleNotFoundTest").ConfigureAwait(false); + using ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval"); methodInvocation.SetPayloadJson("10"); @@ -571,8 +571,8 @@ private async Task SendMethodAndUnsubscribeAsync( TimeSpan responseTimeout = default, ServiceClientTransportSettings serviceClientTransportSettings = default) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await subscribeAndUnsubscribeMethod(deviceClient, MethodName, Logger).ConfigureAwait(false); @@ -587,8 +587,8 @@ private async Task SendMethodAndRespondAsync( TimeSpan responseTimeout = default, ServiceClientTransportSettings serviceClientTransportSettings = default) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); Task methodReceivedTask = await setDeviceReceiveMethod(deviceClient, MethodName, Logger).ConfigureAwait(false); Task serviceSendTask = ServiceSendMethodAndVerifyResponseAsync( @@ -608,7 +608,7 @@ private async Task SendMethodAndRespondAsync( private async Task SendMethodAndRespondAsync(Client.TransportType transport, Func> setDeviceReceiveMethod, TimeSpan responseTimeout = default, ServiceClientTransportSettings serviceClientTransportSettings = default) { TestModule testModule = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); - using var moduleClient = ModuleClient.CreateFromConnectionString(testModule.ConnectionString, transport); + using ModuleClient moduleClient = ModuleClient.CreateFromConnectionString(testModule.ConnectionString, transport); Task methodReceivedTask = await setDeviceReceiveMethod(moduleClient, MethodName, Logger).ConfigureAwait(false); diff --git a/e2e/test/iothub/service/BulkOperationsE2ETests.cs b/e2e/test/iothub/service/BulkOperationsE2ETests.cs index f748b217fa..53ebc5c813 100644 --- a/e2e/test/iothub/service/BulkOperationsE2ETests.cs +++ b/e2e/test/iothub/service/BulkOperationsE2ETests.cs @@ -24,8 +24,8 @@ public async Task BulkOperations_UpdateTwins2Device_Ok() var tagName = Guid.NewGuid().ToString(); var tagValue = Guid.NewGuid().ToString(); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Id).ConfigureAwait(false); @@ -51,8 +51,8 @@ public async Task BulkOperations_UpdateTwins2DevicePatch_Ok() var tagName = Guid.NewGuid().ToString(); var tagValue = Guid.NewGuid().ToString(); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = new Twin(); twin.DeviceId = testDevice.Id; @@ -79,7 +79,7 @@ public async Task BulkOperations_UpdateTwins2Module_Ok() var tagValue = Guid.NewGuid().ToString(); TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); @@ -108,7 +108,7 @@ public async Task BulkOperations_UpdateTwins2ModulePatch_Ok() TestModule testModule = await TestModule.GetTestModuleAsync(DevicePrefix, ModulePrefix, Logger).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twin = new Twin(); twin.DeviceId = testModule.DeviceId; twin.ModuleId = testModule.Id; diff --git a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs index 435998214f..3aafb1c217 100644 --- a/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs +++ b/e2e/test/iothub/service/DigitalTwinClientE2ETests.cs @@ -31,7 +31,7 @@ public class DigitalTwinClientE2ETests : E2EMsTestBase public async Task DigitalTwinWithOnlyRootComponentOperationsAsync() { // Create a new test device instance. - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); string deviceId = testDevice.Id; try @@ -103,7 +103,7 @@ await deviceClient.SetMethodHandlerAsync(commandName, public async Task DigitalTwinWithComponentOperationsAsync() { // Create a new test device instance. - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); string deviceId = testDevice.Id; try diff --git a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs index 0ebf0f1c5b..151d21bb51 100644 --- a/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs +++ b/e2e/test/iothub/service/IoTHubCertificateValidationE2ETest.cs @@ -18,9 +18,9 @@ public class IoTHubCertificateValidationE2ETest : E2EMsTestBase [LoggedTestMethod] public async Task RegistryManager_QueryDevicesInvalidServiceCertificateHttp_Fails() { - var rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); + using RegistryManager rm = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); IQuery query = rm.CreateQuery("select * from devices"); - var exception = await Assert.ThrowsExceptionAsync( + IotHubCommunicationException exception = await Assert.ThrowsExceptionAsync( () => query.GetNextAsTwinAsync()).ConfigureAwait(false); #if NET451 || NET472 @@ -50,18 +50,19 @@ public async Task ServiceClient_SendMessageToDeviceInvalidServiceCertificateAmqp private static async Task TestServiceClientInvalidServiceCertificate(TransportType transport) { - var service = ServiceClient.CreateFromConnectionString( + using ServiceClient service = ServiceClient.CreateFromConnectionString( TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate, transport); - await service.SendAsync("testDevice1", new Message()).ConfigureAwait(false); + using var testMessage = new Message(); + await service.SendAsync("testDevice1", testMessage).ConfigureAwait(false); } [LoggedTestMethod] public async Task JobClient_ScheduleTwinUpdateInvalidServiceCertificateHttp_Fails() { - var job = JobClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); + using JobClient jobClient = JobClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionStringInvalidServiceCertificate); var exception = await Assert.ThrowsExceptionAsync( - () => job.ScheduleTwinUpdateAsync( + () => jobClient.ScheduleTwinUpdateAsync( "testDevice", "DeviceId IN ['testDevice']", new Shared.Twin(), @@ -132,7 +133,8 @@ private static async Task TestDeviceClientInvalidServiceCertificate(Client.Trans TestConfiguration.IoTHub.DeviceConnectionStringInvalidServiceCertificate, transport)) { - await deviceClient.SendEventAsync(new Client.Message()).ConfigureAwait(false); + using var testMessage = new Client.Message(); + await deviceClient.SendEventAsync(testMessage).ConfigureAwait(false); await deviceClient.CloseAsync().ConfigureAwait(false); } } diff --git a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs index 042d6b854f..0cf3a69c07 100644 --- a/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs +++ b/e2e/test/iothub/service/IoTHubServiceProxyE2ETests.cs @@ -57,7 +57,7 @@ public async Task JobClient_ScheduleAndRunTwinJob_WithProxy() private async Task SendSingleMessageService(ServiceClientTransportSettings transportSettings) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); using (DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString)) using (ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(s_connectionString, TransportType.Amqp, transportSettings)) { diff --git a/e2e/test/iothub/service/PnpServiceTests.cs b/e2e/test/iothub/service/PnpServiceTests.cs index b88e2c9e5d..631309c795 100644 --- a/e2e/test/iothub/service/PnpServiceTests.cs +++ b/e2e/test/iothub/service/PnpServiceTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using FluentAssertions; @@ -31,19 +32,19 @@ public async Task DeviceTwin_Contains_ModelId() // Setup // Create a device. - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); // Send model ID with MQTT connect packet to make the device plug and play. var options = new ClientOptions { ModelId = TestModelId, }; - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, Client.TransportType.Mqtt_Tcp_Only, options); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, Client.TransportType.Mqtt_Tcp_Only, options); await deviceClient.OpenAsync().ConfigureAwait(false); // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -59,21 +60,22 @@ public async Task DeviceTwin_Contains_ModelId_X509() // Setup // Create a device. - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, TestDeviceType.X509).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix, TestDeviceType.X509).ConfigureAwait(false); // Send model ID with MQTT connect packet to make the device plug and play. var options = new ClientOptions { ModelId = TestModelId, }; string hostName = HostNameHelper.GetHostName(TestConfiguration.IoTHub.ConnectionString); - var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, TestConfiguration.IoTHub.GetCertificateWithPrivateKey()); - using var deviceClient = DeviceClient.Create(hostName, auth, Client.TransportType.Mqtt_Tcp_Only, options); + X509Certificate2 authCertificate = TestConfiguration.IoTHub.GetCertificateWithPrivateKey(); + using var auth = new DeviceAuthenticationWithX509Certificate(testDevice.Id, authCertificate); + using DeviceClient deviceClient = DeviceClient.Create(hostName, auth, Client.TransportType.Mqtt_Tcp_Only, options); await deviceClient.OpenAsync().ConfigureAwait(false); // Act // Get device twin. - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testDevice.Device.Id).ConfigureAwait(false); // Assert @@ -81,6 +83,13 @@ public async Task DeviceTwin_Contains_ModelId_X509() // Cleanup await registryManager.RemoveDeviceAsync(testDevice.Id).ConfigureAwait(false); + + // X509Certificate needs to be disposed for implementations !NET451 (NET451 doesn't implement X509Certificates as IDisposable). + if (authCertificate is IDisposable disposableCert) + { + disposableCert?.Dispose(); + } + authCertificate = null; } [TestMethod] @@ -95,13 +104,13 @@ public async Task ModuleTwin_Contains_ModelId() { ModelId = TestModelId, }; - using var moduleClient = ModuleClient.CreateFromConnectionString(testModule.ConnectionString, Client.TransportType.Mqtt_Tcp_Only, options); + using ModuleClient moduleClient = ModuleClient.CreateFromConnectionString(testModule.ConnectionString, Client.TransportType.Mqtt_Tcp_Only, options); await moduleClient.OpenAsync().ConfigureAwait(false); // Act // Get module twin. - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Twin twin = await registryManager.GetTwinAsync(testModule.DeviceId, testModule.Id).ConfigureAwait(false); // Assert diff --git a/e2e/test/iothub/service/RegistryManagerE2ETests.cs b/e2e/test/iothub/service/RegistryManagerE2ETests.cs index 9d93ffccc2..0e4c681fcb 100644 --- a/e2e/test/iothub/service/RegistryManagerE2ETests.cs +++ b/e2e/test/iothub/service/RegistryManagerE2ETests.cs @@ -27,7 +27,7 @@ public class RegistryManagerE2ETests : E2EMsTestBase public async Task RegistryManager_BadProxy_ThrowsException() { // arrange - var registryManager = RegistryManager.CreateFromConnectionString( + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString( TestConfiguration.IoTHub.ConnectionString, new HttpTransportSettings { @@ -95,41 +95,39 @@ public async Task RegistryManager_AddDeviceWithTwinWithDeviceCapabilities() { string deviceId = _devicePrefix + Guid.NewGuid(); - using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString)) + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + var twin = new Twin { - var twin = new Twin - { - Tags = new TwinCollection(@"{ companyId: 1234 }"), - }; + Tags = new TwinCollection(@"{ companyId: 1234 }"), + }; - var iotEdgeDevice = new Device(deviceId) - { - Capabilities = new DeviceCapabilities { IotEdge = true } - }; + var iotEdgeDevice = new Device(deviceId) + { + Capabilities = new DeviceCapabilities { IotEdge = true } + }; - await registryManager.AddDeviceWithTwinAsync(iotEdgeDevice, twin).ConfigureAwait(false); + await registryManager.AddDeviceWithTwinAsync(iotEdgeDevice, twin).ConfigureAwait(false); - Device actual = await registryManager.GetDeviceAsync(deviceId).ConfigureAwait(false); - await registryManager.RemoveDeviceAsync(deviceId).ConfigureAwait(false); + Device actual = await registryManager.GetDeviceAsync(deviceId).ConfigureAwait(false); + await registryManager.RemoveDeviceAsync(deviceId).ConfigureAwait(false); - Assert.IsTrue(actual.Capabilities.IotEdge); - } + Assert.IsTrue(actual.Capabilities.IotEdge); } [LoggedTestMethod] public async Task RegistryManager_BulkLifecycle() { int bulkCount = 50; - List devices = new List(); + var devices = new List(); for (int i = 0; i < bulkCount; i++) { devices.Add(new Device(_devicePrefix + Guid.NewGuid())); } - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); // Test that you can create devices in bulk - var bulkAddResult = await registryManager.AddDevices2Async(devices).ConfigureAwait(false); + BulkRegistryOperationResult bulkAddResult = await registryManager.AddDevices2Async(devices).ConfigureAwait(false); Assert.IsTrue(bulkAddResult.IsSuccessful); foreach (Device device in devices) @@ -138,7 +136,7 @@ public async Task RegistryManager_BulkLifecycle() Assert.IsNotNull(await registryManager.GetDeviceAsync(device.Id).ConfigureAwait(false)); } - List twins = new List(); + var twins = new List(); string expectedProperty = "someNewProperty"; string expectedPropertyValue = "someNewPropertyValue"; foreach (Device device in devices) @@ -159,7 +157,7 @@ public async Task RegistryManager_BulkLifecycle() } // Test that you can delete device identities in bulk - var bulkDeleteResult = await registryManager.RemoveDevices2Async(devices, true, default).ConfigureAwait(false); + BulkRegistryOperationResult bulkDeleteResult = await registryManager.RemoveDevices2Async(devices, true, default).ConfigureAwait(false); Assert.IsTrue(bulkDeleteResult.IsSuccessful); @@ -179,18 +177,16 @@ public async Task RegistryManager_AddDeviceWithProxy() Proxy = new WebProxy(TestConfiguration.IoTHub.ProxyServerAddress) }; - using (var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportSettings)) - { - var device = new Device(deviceId); - await registryManager.AddDeviceAsync(device).ConfigureAwait(false); - } + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportSettings); + var device = new Device(deviceId); + await registryManager.AddDeviceAsync(device).ConfigureAwait(false); } [LoggedTestMethod] public async Task RegistryManager_Query_Works() { // arrange - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); string deviceId = $"{_devicePrefix}{Guid.NewGuid()}"; try @@ -210,7 +206,7 @@ public async Task RegistryManager_Query_Works() twins = await query.GetNextAsTwinAsync().ConfigureAwait(false); - if (twins.Count() > 0) + if (twins.Any()) { break; } @@ -242,7 +238,7 @@ public async Task ModulesClient_GetModulesOnDevice() } Device device = null; - RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -285,7 +281,7 @@ public async Task ModulesClient_IdentityLifecycle() string testDeviceId = $"IdentityLifecycleDevice{Guid.NewGuid()}"; string testModuleId = $"IdentityLifecycleModule{Guid.NewGuid()}"; - RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); try { @@ -328,8 +324,8 @@ public async Task ModulesClient_IdentityLifecycle() [LoggedTestMethod] public async Task ModulesClient_DeviceTwinLifecycle() { - RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); - var module = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); + using RegistryManager client = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + TestModule module = await TestModule.GetTestModuleAsync(_devicePrefix, _modulePrefix, Logger).ConfigureAwait(false); try { diff --git a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs index 1b1b8795be..33c5c7c20e 100644 --- a/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerExportDevicesTests.cs @@ -56,7 +56,7 @@ public async Task RegistryManager_ExportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string edgeId = $"{nameof(RegistryManager_ExportDevices)}-Edge-{StorageContainer.GetRandomSuffix(4)}"; string deviceId = $"{nameof(RegistryManager_ExportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); diff --git a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs index 21bad7bb87..56d8fa9ed1 100644 --- a/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs +++ b/e2e/test/iothub/service/RegistryManagerImportDevicesTests.cs @@ -49,7 +49,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag StorageContainer storageContainer = null; string deviceId = $"{nameof(RegistryManager_ImportDevices)}-{StorageContainer.GetRandomSuffix(4)}"; - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Logger.Trace($"Using deviceId {deviceId}"); @@ -65,7 +65,7 @@ public async Task RegistryManager_ImportDevices(StorageAuthenticationType storag ? storageContainer.SasUri : storageContainer.Uri; - Stream devicesFile = ImportExportDevicesHelpers.BuildDevicesStream( + using Stream devicesFile = ImportExportDevicesHelpers.BuildDevicesStream( new List { new ExportImportDevice( diff --git a/e2e/test/iothub/service/ServiceClientE2ETests.cs b/e2e/test/iothub/service/ServiceClientE2ETests.cs index 3f5d8a11f8..ebad2565b7 100644 --- a/e2e/test/iothub/service/ServiceClientE2ETests.cs +++ b/e2e/test/iothub/service/ServiceClientE2ETests.cs @@ -47,7 +47,7 @@ private async Task DefaultTimeout() private async Task TestTimeout(TimeSpan? timeout) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); Stopwatch sw = new Stopwatch(); @@ -56,7 +56,8 @@ private async Task TestTimeout(TimeSpan? timeout) Logger.Trace($"Testing ServiceClient SendAsync() timeout in ticks={timeout?.Ticks}"); try { - await sender.SendAsync(testDevice.Id, new Message(Encoding.ASCII.GetBytes("Dummy Message")), timeout).ConfigureAwait(false); + using var testMessage = new Message(Encoding.ASCII.GetBytes("Test Message")); + await sender.SendAsync(testDevice.Id, testMessage, timeout).ConfigureAwait(false); } finally { @@ -71,12 +72,12 @@ private async Task TestTimeout(TimeSpan? timeout) public async Task ServiceClient_SendsMessage(TransportType transportType) { // arrange - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportType); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using ServiceClient sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, transportType); string messageId = Guid.NewGuid().ToString(); // act and expect no exception - var message = new Message + using var message = new Message { MessageId = messageId, }; @@ -90,13 +91,13 @@ public async Task ServiceClient_SendsMessage(TransportType transportType) public async Task MessageIdDefaultNotSet_SendEventDoesNotSetMessageId() { // arrange - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using ServiceClient sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); string messageId = Guid.NewGuid().ToString(); // act - var messageWithoutId = new Message(); - var messageWithId = new Message + using var messageWithoutId = new Message(); + using var messageWithId = new Message { MessageId = messageId, }; @@ -115,17 +116,17 @@ public async Task MessageIdDefaultNotSet_SendEventDoesNotSetMessageId() public async Task MessageIdDefaultSetToNull_SendEventDoesNotSetMessageId() { // arrange - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); var options = new ServiceClientOptions { SdkAssignsMessageId = Shared.SdkAssignsMessageId.Never, }; - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); + using ServiceClient sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act - var messageWithoutId = new Message(); - var messageWithId = new Message + using var messageWithoutId = new Message(); + using var messageWithId = new Message { MessageId = messageId, }; @@ -144,17 +145,17 @@ public async Task MessageIdDefaultSetToNull_SendEventDoesNotSetMessageId() public async Task MessageIdDefaultSetToGuid_SendEventSetMessageIdIfNotSet() { // arrange - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, DevicePrefix).ConfigureAwait(false); var options = new ServiceClientOptions { SdkAssignsMessageId = Shared.SdkAssignsMessageId.WhenUnset, }; - using var sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); + using ServiceClient sender = ServiceClient.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString, options); string messageId = Guid.NewGuid().ToString(); // act - var messageWithoutId = new Message(); - var messageWithId = new Message + using var messageWithoutId = new Message(); + using var messageWithId = new Message { MessageId = messageId, }; diff --git a/e2e/test/iothub/twin/FaultInjectionPoolAmqpTests.TwinFaultInjectionPoolAmqpTests.cs b/e2e/test/iothub/twin/FaultInjectionPoolAmqpTests.TwinFaultInjectionPoolAmqpTests.cs index 9dc7ca5968..53736ec57c 100644 --- a/e2e/test/iothub/twin/FaultInjectionPoolAmqpTests.TwinFaultInjectionPoolAmqpTests.cs +++ b/e2e/test/iothub/twin/FaultInjectionPoolAmqpTests.TwinFaultInjectionPoolAmqpTests.cs @@ -1439,12 +1439,9 @@ async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, await TwinE2ETests.Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString(), Logger).ConfigureAwait(false); } - async Task CleanupOperationAsync(IList deviceClients) + async Task CleanupOperationAsync(List deviceClients, List _) { - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); await Task.FromResult(false).ConfigureAwait(false); } @@ -1481,13 +1478,9 @@ private async Task Twin_DeviceDesiredPropertyUpdateRecoveryPoolOverAmqp( string proxyAddress = null) { var twinPropertyMap = new Dictionary>(); - var testDevicesWithCallbackHandler = new Dictionary(); - async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) + async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { - var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); - testDevicesWithCallbackHandler.Add(testDevice.Id, testDeviceCallbackHandler); - var propName = Guid.NewGuid().ToString(); var propValue = Guid.NewGuid().ToString(); twinPropertyMap.Add(testDevice.Id, new List { propName, propValue }); @@ -1497,9 +1490,8 @@ async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice, await testDeviceCallbackHandler.SetTwinPropertyUpdateCallbackHandlerAsync(propName).ConfigureAwait(false); } - async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _) + async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler testDeviceCallbackHandler) { - TestDeviceCallbackHandler testDeviceCallbackHandler = testDevicesWithCallbackHandler[testDevice.Id]; using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); List twinProperties = twinPropertyMap[testDevice.Id]; @@ -1515,21 +1507,12 @@ async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice, await Task.WhenAll(serviceSendTask, twinReceivedTask).ConfigureAwait(false); } - async Task CleanupOperationAsync(IList deviceClients) + async Task CleanupOperationAsync(List deviceClients, List testDeviceCallbackHandlers) { - foreach (DeviceClient deviceClient in deviceClients) - { - deviceClient.Dispose(); - } - - foreach (KeyValuePair entry in testDevicesWithCallbackHandler) - { - TestDeviceCallbackHandler testDeviceCallbackHandler = entry.Value; - testDeviceCallbackHandler?.Dispose(); - } + deviceClients.ForEach(deviceClient => deviceClient.Dispose()); + testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); twinPropertyMap.Clear(); - testDevicesWithCallbackHandler.Clear(); await Task.FromResult(false).ConfigureAwait(false); } diff --git a/e2e/test/iothub/twin/TwinE2ETests.cs b/e2e/test/iothub/twin/TwinE2ETests.cs index 48dbbaa582..be9d0aecca 100644 --- a/e2e/test/iothub/twin/TwinE2ETests.cs +++ b/e2e/test/iothub/twin/TwinE2ETests.cs @@ -391,8 +391,8 @@ public async Task Twin_ClientSetsReportedPropertyWithoutDesiredPropertyCallback( { // arrange - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transportType); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transportType); await Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString(), Logger).ConfigureAwait(false); @@ -417,16 +417,16 @@ public async Task Twin_ClientSetsReportedPropertyWithoutDesiredPropertyCallback( private async Task Twin_DeviceSetsReportedPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString(), Logger).ConfigureAwait(false); } private async Task Twin_DeviceSetsReportedPropertyArrayAndGetsItBackSingleDeviceAsync(Client.TransportType transport) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, s_listOfPropertyValues, Logger).ConfigureAwait(false); } @@ -523,7 +523,7 @@ await deviceClient public static async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, object propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -538,8 +538,8 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceUnsubscribes(Client.T Logger.Trace($"{nameof(Twin_ServiceSetsDesiredPropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); // Set a callback await deviceClient. @@ -573,8 +573,8 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesEventAsync(Cl Logger.Trace($"{nameof(Twin_ServiceSetsDesiredPropertyAndDeviceReceivesEventAsync)}: name={propName}, value={propValue}"); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); Task updateReceivedTask = await setTwinPropertyUpdateCallbackAsync(deviceClient, propName, propValue, Logger).ConfigureAwait(false); @@ -601,9 +601,9 @@ private async Task Twin_ServiceSetsDesiredPropertyAndDeviceReceivesItOnNextGetAs var propName = Guid.NewGuid().ToString(); var propValue = Guid.NewGuid().ToString(); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -621,9 +621,9 @@ private async Task Twin_DeviceSetsReportedPropertyAndServiceReceivesItAsync(Clie var propName = Guid.NewGuid().ToString(); var propValue = Guid.NewGuid().ToString(); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var patch = new TwinCollection(); patch[propName] = propValue; @@ -642,9 +642,9 @@ private async Task Twin_ServiceDoesNotCreateNullPropertyInCollectionAsync(Client var propName2 = Guid.NewGuid().ToString(); var propEmptyValue = "{}"; - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); await deviceClient .UpdateReportedPropertiesAsync( @@ -693,9 +693,9 @@ private async Task Twin_ClientHandlesRejectionInvalidPropertyNameAsync(Client.Tr var propName1 = "$" + Guid.NewGuid().ToString(); var propName2 = Guid.NewGuid().ToString(); - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); + using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); var exceptionThrown = false; try diff --git a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs index 0f163d994f..0d76a46aec 100644 --- a/e2e/test/iothub/twin/TwinFaultInjectionTests.cs +++ b/e2e/test/iothub/twin/TwinFaultInjectionTests.cs @@ -256,7 +256,7 @@ await FaultInjection private async Task RegistryManagerUpdateDesiredPropertyAsync(string deviceId, string propName, string propValue) { - using var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); var twinPatch = new Twin(); twinPatch.Properties.Desired[propName] = propValue; @@ -273,7 +273,7 @@ private async Task Twin_DeviceDesiredPropertyUpdateRecoveryAsync( string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; - var registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); + using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); var propName = Guid.NewGuid().ToString(); diff --git a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs index 21482a1ec7..c794e82e76 100644 --- a/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs +++ b/e2e/test/provisioning/ProvisioningCertificateValidationE2ETest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Azure.Devices.Provisioning.Client; using Microsoft.Azure.Devices.Provisioning.Client.Transport; @@ -18,7 +19,7 @@ public class ProvisioningCertificateValidationE2ETest : E2EMsTestBase [LoggedTestMethod] public async Task ProvisioningServiceClient_QueryInvalidServiceCertificateHttp_Fails() { - using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString( + using ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString( TestConfiguration.Provisioning.ConnectionStringInvalidServiceCertificate); Query q = provisioningServiceClient.CreateEnrollmentGroupQuery( new QuerySpecification("SELECT * FROM enrollmentGroups")); @@ -96,8 +97,8 @@ public async Task ProvisioningDeviceClient_RegisterAsyncInvalidServiceCertificat private static async Task TestInvalidServiceCertificate(ProvisioningTransportHandler transport) { - using var security = - new SecurityProviderX509Certificate(TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate()); + using X509Certificate2 cert = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + using var security = new SecurityProviderX509Certificate(cert); ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.Create( TestConfiguration.Provisioning.GlobalDeviceEndpointInvalidServiceCertificate, "0ne00000001", diff --git a/e2e/test/provisioning/ProvisioningE2ETests.cs b/e2e/test/provisioning/ProvisioningE2ETests.cs index 201681fd20..5f56f48c22 100644 --- a/e2e/test/provisioning/ProvisioningE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningE2ETests.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Client; +using Microsoft.Azure.Devices.E2ETests.Helpers; using Microsoft.Azure.Devices.Provisioning.Client; using Microsoft.Azure.Devices.Provisioning.Client.Transport; using Microsoft.Azure.Devices.Provisioning.Security.Samples; @@ -34,6 +35,8 @@ public class ProvisioningE2ETests : E2EMsTestBase private const string InvalidGlobalAddress = "httpbin.org"; private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; private static readonly string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; + private static readonly X509Certificate2 s_individualEnrollmentCertificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + private static readonly X509Certificate2 s_groupEnrollmentCertificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); private readonly string _idPrefix = $"e2e-{nameof(ProvisioningE2ETests).ToLower()}-"; private readonly VerboseTestLogger _verboseLog = VerboseTestLogger.GetInstance(); @@ -373,7 +376,7 @@ private async Task ProvisioningDeviceClientCustomAllocationPolicyAsync( bool setCustomProxy, string customServerProxy = null) { - var closeHostName = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName; + string closeHostName = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString).HostName; ICollection iotHubsToProvisionTo = new List() { closeHostName, TestConfiguration.Provisioning.FarAwayIotHubHostName }; string expectedDestinationHub = ""; @@ -556,7 +559,7 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( transport.Proxy = (proxyServerAddress != null) ? new WebProxy(s_proxyServerAddress) : null; } - var provClient = ProvisioningDeviceClient.Create( + ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, TestConfiguration.Provisioning.IdScope, security, @@ -595,14 +598,23 @@ private async Task ProvisioningDeviceClientValidRegistrationIdRegisterOkAsync( ValidateDeviceRegistrationResult(false, result); +#pragma warning disable CA2000 // Dispose objects before losing scope + // The certificate instance referenced in the DeviceAuthenticationWithX509Certificate instance is common for all tests in this class. It is disposed during class cleanup. Client.IAuthenticationMethod auth = CreateAuthenticationMethodFromSecurityProvider(security, result.DeviceId); +#pragma warning restore CA2000 // Dispose objects before losing scope await ConfirmRegisteredDeviceWorksAsync(result, auth, transportType, false).ConfigureAwait(false); await ConfirmExpectedDeviceCapabilitiesAsync(result, auth, deviceCapabilities).ConfigureAwait(false); if (attestationType != AttestationMechanismType.X509) //x509 enrollments are hardcoded, should never be deleted { - await DeleteCreatedEnrollmentAsync(enrollmentType, CreateProvisioningService(proxyServerAddress), security, groupId).ConfigureAwait(false); + using ProvisioningServiceClient dpsServiceClient = CreateProvisioningService(proxyServerAddress); + await DeleteCreatedEnrollmentAsync(enrollmentType, dpsServiceClient, security, groupId).ConfigureAwait(false); + } + + if (auth is IDisposable disposableAuth) + { + disposableAuth?.Dispose(); } } @@ -615,7 +627,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc string expectedDestinationHub, string proxyServerAddress = null) { - ProvisioningServiceClient provisioningServiceClient = CreateProvisioningService(s_proxyServerAddress); + using ProvisioningServiceClient provisioningServiceClient = CreateProvisioningService(s_proxyServerAddress); string groupId = _idPrefix + AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); var customAllocationDefinition = new CustomAllocationDefinition @@ -641,7 +653,7 @@ private async Task ProvisioningDeviceClientProvisioningFlowCustomAllocationAlloc transport.Proxy = (proxyServerAddress != null) ? new WebProxy(s_proxyServerAddress) : null; } - var provClient = ProvisioningDeviceClient.Create( + ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, TestConfiguration.Provisioning.IdScope, security, @@ -708,7 +720,7 @@ public async Task ProvisioningDeviceClient_InvalidRegistrationId_TpmRegister_Fai { using ProvisioningTransportHandler transport = CreateTransportHandlerFromName(transportProtocol); using SecurityProvider security = new SecurityProviderTpmSimulator("invalidregistrationid"); - var provClient = ProvisioningDeviceClient.Create( + ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( s_globalDeviceEndpoint, TestConfiguration.Provisioning.IdScope, security, @@ -921,15 +933,16 @@ public static ProvisioningTransportHandler CreateTransportHandlerFromName(Client /// /// Attempt to create device client instance from provided arguments, ensure that it can open a /// connection, ensure that it can send telemetry, and (optionally) send a reported property update - /// + /// CreateSecurityProviderFromNameAsync(Attesta { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromNameAsync)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + using ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); switch (attestationType) { case AttestationMechanismType.Tpm: string registrationId = AttestationTypeToString(attestationType) + "-registration-id-" + Guid.NewGuid(); var tpmSim = new SecurityProviderTpmSimulator(registrationId); - string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); - Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); - IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; - IndividualEnrollment enrollment = await provisioningService.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment).ConfigureAwait(false); + var individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; + IndividualEnrollment enrollment = await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment).ConfigureAwait(false); var attestation = new TpmAttestation(base64Ek); enrollment.Attestation = attestation; Logger.Trace($"Updating enrollment: RegistrationID = {registrationId} EK = '{base64Ek}'"); - await provisioningService.CreateOrUpdateIndividualEnrollmentAsync(enrollment).ConfigureAwait(false); - + await provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(enrollment).ConfigureAwait(false); return tpmSim; case AttestationMechanismType.X509: @@ -987,11 +996,11 @@ private async Task CreateSecurityProviderFromNameAsync(Attesta switch (enrollmentType) { case EnrollmentType.Individual: - certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = s_individualEnrollmentCertificate; break; case EnrollmentType.Group: - certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); + certificate = s_groupEnrollmentCertificate; collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); break; @@ -1044,26 +1053,26 @@ private Client.IAuthenticationMethod CreateAuthenticationMethodFromSecurityProvi { _verboseLog.WriteLine($"{nameof(CreateAuthenticationMethodFromSecurityProvider)}({deviceId})"); - if (provisioningSecurity is SecurityProviderTpm) + Client.IAuthenticationMethod auth; + if (provisioningSecurity is SecurityProviderTpm tpmSecurity) + { + auth = new DeviceAuthenticationWithTpm(deviceId, tpmSecurity); + } + else if (provisioningSecurity is SecurityProviderX509 x509Security) { - var security = (SecurityProviderTpm)provisioningSecurity; - var auth = new DeviceAuthenticationWithTpm(deviceId, security); - return auth; + X509Certificate2 cert = x509Security.GetAuthenticationCertificate(); + auth = new DeviceAuthenticationWithX509Certificate(deviceId, cert); } - else if (provisioningSecurity is SecurityProviderX509) + else if (provisioningSecurity is SecurityProviderSymmetricKey symmetricKeySecurity) { - var security = (SecurityProviderX509)provisioningSecurity; - X509Certificate2 cert = security.GetAuthenticationCertificate(); - return new DeviceAuthenticationWithX509Certificate(deviceId, cert); + auth = new DeviceAuthenticationWithRegistrySymmetricKey(deviceId, symmetricKeySecurity.GetPrimaryKey()); } - else if (provisioningSecurity is SecurityProviderSymmetricKey) + else { - var security = (SecurityProviderSymmetricKey)provisioningSecurity; - var auth = new DeviceAuthenticationWithRegistrySymmetricKey(deviceId, security.GetPrimaryKey()); - return auth; + throw new NotSupportedException($"Unknown provisioningSecurity type."); } - throw new NotSupportedException($"Unknown provisioningSecurity type."); + return auth; } /// @@ -1146,5 +1155,12 @@ public static bool ImplementsWebProxy(Client.TransportType transportProtocol) throw new NotSupportedException($"Unknown transport: '{transportProtocol}'."); } + + [ClassCleanup] + public static void CleanupCertificates() + { + s_individualEnrollmentCertificate?.Dispose(); + s_groupEnrollmentCertificate?.Dispose(); + } } } diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 79a540cb82..72ee57237b 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -114,7 +114,7 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation_Symmet public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + using ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); IndividualEnrollment individualEnrollment = await CreateIndividualEnrollment(provisioningServiceClient, attestationType, null, AllocationPolicy.Static, null, null, null); AttestationMechanism attestationMechanism = await provisioningServiceClient.GetIndividualEnrollmentAttestationAsync(individualEnrollment.RegistrationId); @@ -144,7 +144,7 @@ public async Task ProvisioningServiceClient_GetIndividualEnrollmentAttestation(A public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation(AttestationMechanismType attestationType) { - ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + using ProvisioningServiceClient provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); string groupId = AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); EnrollmentGroup enrollmentGroup = await CreateEnrollmentGroup(provisioningServiceClient, attestationType, groupId, null, AllocationPolicy.Static, null, null, null); @@ -174,7 +174,7 @@ public async Task ProvisioningServiceClient_GetEnrollmentGroupAttestation(Attest /// If the query succeeded, otherwise this method will throw private async Task ProvisioningServiceClient_IndividualEnrollments_Query_Ok(string proxyServerAddress) { - ProvisioningServiceClient provisioningServiceClient = CreateProvisioningService(proxyServerAddress); + using ProvisioningServiceClient provisioningServiceClient = CreateProvisioningService(proxyServerAddress); var querySpecification = new QuerySpecification("SELECT * FROM enrollments"); using (Query query = provisioningServiceClient.CreateIndividualEnrollmentQuery(querySpecification)) { @@ -268,7 +268,7 @@ public static async Task CreateIndividualEnrollment(Provis using (var tpmSim = new SecurityProviderTpmSimulator(registrationId)) { string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + using ProvisioningServiceClient provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { Capabilities = capabilities, diff --git a/e2e/test/provisioning/ReprovisioningE2ETests.cs b/e2e/test/provisioning/ReprovisioningE2ETests.cs index c281e88f34..f647a52d02 100644 --- a/e2e/test/provisioning/ReprovisioningE2ETests.cs +++ b/e2e/test/provisioning/ReprovisioningE2ETests.cs @@ -30,11 +30,11 @@ public class ReprovisioningE2ETests : E2EMsTestBase private const int PassingTimeoutMiliseconds = 10 * 60 * 1000; private static readonly string s_globalDeviceEndpoint = TestConfiguration.Provisioning.GlobalDeviceEndpoint; private static string s_proxyServerAddress = TestConfiguration.IoTHub.ProxyServerAddress; - private readonly string _devicePrefix = $"E2E_{nameof(ProvisioningE2ETests)}_"; + private static readonly X509Certificate2 s_individualEnrollmentCertificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + private static readonly X509Certificate2 s_groupEnrollmentCertificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); -#pragma warning disable CA1823 + private readonly string _devicePrefix = $"E2E_{nameof(ProvisioningE2ETests)}_"; private readonly VerboseTestLogger _verboseLog = VerboseTestLogger.GetInstance(); -#pragma warning restore CA1823 [LoggedTestMethod] public async Task ProvisioningDeviceClient_ReprovisionedDeviceResetsTwin_MqttWs_SymmetricKey_RegisterOk_Individual() @@ -211,7 +211,7 @@ public async Task ProvisioningDeviceClient_ReprovisioningBlockingWorks_MqttWs_Sy /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + IotHubConnectionStringBuilder connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); @@ -223,7 +223,7 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_ResetTwin(Client. /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + IotHubConnectionStringBuilder connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = true, UpdateHubAssignment = true }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); @@ -234,7 +234,7 @@ private async Task ProvisioningDeviceClient_ReprovisioningFlow_KeepTwin(Client.T /// private async Task ProvisioningDeviceClient_ReprovisioningFlow_DoNotReprovision(Client.TransportType transportProtocol, AttestationMechanismType attestationType, EnrollmentType enrollmentType, bool setCustomProxy, string customServerProxy = null) { - var connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); + IotHubConnectionStringBuilder connectionString = IotHubConnectionStringBuilder.Create(TestConfiguration.IoTHub.ConnectionString); ICollection iotHubsToStartAt = new List() { TestConfiguration.Provisioning.FarAwayIotHubHostName }; ICollection iotHubsToReprovisionTo = new List() { connectionString.HostName }; await ProvisioningDeviceClient_ReprovisioningFlow(transportProtocol, attestationType, enrollmentType, setCustomProxy, new ReprovisionPolicy { MigrateDeviceData = false, UpdateHubAssignment = false }, AllocationPolicy.Hashed, null, iotHubsToStartAt, iotHubsToReprovisionTo, customServerProxy).ConfigureAwait(false); @@ -259,7 +259,7 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( ICollection iotHubsToReprovisionTo, string proxyServerAddress = null) { - ProvisioningServiceClient provisioningServiceClient = CreateProvisioningService(s_proxyServerAddress); + using ProvisioningServiceClient provisioningServiceClient = CreateProvisioningService(s_proxyServerAddress); string groupId = _devicePrefix + AttestationTypeToString(attestationType) + "-" + Guid.NewGuid(); bool twinOperationsAllowed = transportProtocol != Client.TransportType.Http1; @@ -276,7 +276,7 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( .ConfigureAwait(false); //Check basic provisioning - if (ProvisioningE2ETests.ImplementsWebProxy(transportProtocol) && setCustomProxy) + if (ImplementsWebProxy(transportProtocol) && setCustomProxy) { transport.Proxy = (proxyServerAddress != null) ? new WebProxy(s_proxyServerAddress) : null; } @@ -289,7 +289,12 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( using var cts = new CancellationTokenSource(PassingTimeoutMiliseconds); DeviceRegistrationResult result = await provClient.RegisterAsync(cts.Token).ConfigureAwait(false); ValidateDeviceRegistrationResult(result); + +#pragma warning disable CA2000 // Dispose objects before losing scope + // The certificate instance referenced in the DeviceAuthenticationWithX509Certificate instance is common for all tests in this class. It is disposed during class cleanup. Client.IAuthenticationMethod auth = CreateAuthenticationMethodFromSecurityProvider(security, result.DeviceId); +#pragma warning restore CA2000 // Dispose objects before losing scope + await ConfirmRegisteredDeviceWorks(result, auth, transportProtocol, twinOperationsAllowed).ConfigureAwait(false); //Check reprovisioning @@ -300,7 +305,12 @@ public async Task ProvisioningDeviceClient_ReprovisioningFlow( if (attestationType != AttestationMechanismType.X509) //x509 enrollments are hardcoded, should never be deleted { - await ProvisioningE2ETests.DeleteCreatedEnrollmentAsync(enrollmentType, provisioningServiceClient, security, groupId).ConfigureAwait(false); + await DeleteCreatedEnrollmentAsync(enrollmentType, provisioningServiceClient, security, groupId).ConfigureAwait(false); + } + + if (auth is IDisposable disposableAuth) + { + disposableAuth?.Dispose(); } } @@ -315,8 +325,9 @@ private async Task ConfirmRegisteredDeviceWorks(DeviceRegistrationResult result, Logger.Trace("DeviceClient OpenAsync."); await iotClient.OpenAsync().ConfigureAwait(false); Logger.Trace("DeviceClient SendEventAsync."); - await iotClient.SendEventAsync( - new Client.Message(Encoding.UTF8.GetBytes("TestMessage"))).ConfigureAwait(false); + + using var message = new Client.Message(Encoding.UTF8.GetBytes("TestMessage")); + await iotClient.SendEventAsync(message).ConfigureAwait(false); if (sendReportedPropertiesUpdate) { @@ -348,7 +359,7 @@ private async Task CreateSecurityProviderFromName(AttestationM { _verboseLog.WriteLine($"{nameof(CreateSecurityProviderFromName)}({attestationType})"); - var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); + using var provisioningServiceClient = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); switch (attestationType) { @@ -358,15 +369,16 @@ private async Task CreateSecurityProviderFromName(AttestationM string base64Ek = Convert.ToBase64String(tpmSim.GetEndorsementKey()); - var provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString); - - Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); - IndividualEnrollment individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; - IndividualEnrollment enrollment = await provisioningService.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment).ConfigureAwait(false); - var attestation = new TpmAttestation(base64Ek); - enrollment.Attestation = attestation; - Logger.Trace($"Updating enrollment: RegistrationID = {registrationId} EK = '{base64Ek}'"); - await provisioningService.CreateOrUpdateIndividualEnrollmentAsync(enrollment).ConfigureAwait(false); + using (ProvisioningServiceClient provisioningService = ProvisioningServiceClient.CreateFromConnectionString(TestConfiguration.Provisioning.ConnectionString)) + { + Logger.Trace($"Getting enrollment: RegistrationID = {registrationId}"); + var individualEnrollment = new IndividualEnrollment(registrationId, new TpmAttestation(base64Ek)) { AllocationPolicy = allocationPolicy, ReprovisionPolicy = reprovisionPolicy, IotHubs = iothubs, CustomAllocationDefinition = customAllocationDefinition, Capabilities = capabilities }; + IndividualEnrollment enrollment = await provisioningService.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment).ConfigureAwait(false); + var attestation = new TpmAttestation(base64Ek); + enrollment.Attestation = attestation; + Logger.Trace($"Updating enrollment: RegistrationID = {registrationId} EK = '{base64Ek}'"); + await provisioningService.CreateOrUpdateIndividualEnrollmentAsync(enrollment).ConfigureAwait(false); + } return tpmSim; @@ -377,11 +389,11 @@ private async Task CreateSecurityProviderFromName(AttestationM switch (enrollmentType) { case EnrollmentType.Individual: - certificate = TestConfiguration.Provisioning.GetIndividualEnrollmentCertificate(); + certificate = s_individualEnrollmentCertificate; break; case EnrollmentType.Group: - certificate = TestConfiguration.Provisioning.GetGroupEnrollmentCertificate(); + certificate = s_groupEnrollmentCertificate; collection = TestConfiguration.Provisioning.GetGroupEnrollmentChain(); break; @@ -434,26 +446,26 @@ private Client.IAuthenticationMethod CreateAuthenticationMethodFromSecurityProvi { _verboseLog.WriteLine($"{nameof(CreateAuthenticationMethodFromSecurityProvider)}({deviceId})"); - if (provisioningSecurity is SecurityProviderTpm) + Client.IAuthenticationMethod auth; + if (provisioningSecurity is SecurityProviderTpm tpmSecurity) { - var security = (SecurityProviderTpm)provisioningSecurity; - var auth = new DeviceAuthenticationWithTpm(deviceId, security); - return auth; + auth = new DeviceAuthenticationWithTpm(deviceId, tpmSecurity); } - else if (provisioningSecurity is SecurityProviderX509) + else if (provisioningSecurity is SecurityProviderX509 x509Security) { - var security = (SecurityProviderX509)provisioningSecurity; - X509Certificate2 cert = security.GetAuthenticationCertificate(); - return new DeviceAuthenticationWithX509Certificate(deviceId, cert); + X509Certificate2 cert = x509Security.GetAuthenticationCertificate(); + auth = new DeviceAuthenticationWithX509Certificate(deviceId, cert); } - else if (provisioningSecurity is SecurityProviderSymmetricKey) + else if (provisioningSecurity is SecurityProviderSymmetricKey symmetricKeySecurity) + { + auth = new DeviceAuthenticationWithRegistrySymmetricKey(deviceId, symmetricKeySecurity.GetPrimaryKey()); + } + else { - var security = (SecurityProviderSymmetricKey)provisioningSecurity; - var auth = new DeviceAuthenticationWithRegistrySymmetricKey(deviceId, security.GetPrimaryKey()); - return auth; + throw new NotSupportedException($"Unknown provisioningSecurity type."); } - throw new NotSupportedException($"Unknown provisioningSecurity type."); + return auth; } /// @@ -518,8 +530,9 @@ private async Task ConfirmDeviceWorksAfterReprovisioning(DeviceRegistrationResul Logger.Trace("DeviceClient OpenAsync."); await iotClient.OpenAsync().ConfigureAwait(false); Logger.Trace("DeviceClient SendEventAsync."); - await iotClient.SendEventAsync( - new Client.Message(Encoding.UTF8.GetBytes("TestMessage"))).ConfigureAwait(false); + + using var testMessage = new Client.Message(Encoding.UTF8.GetBytes("TestMessage")); + await iotClient.SendEventAsync(testMessage).ConfigureAwait(false); //twin can be configured to revert back to default twin when provisioned, or to keep twin // from previous hub's records. @@ -549,5 +562,12 @@ await iotClient.SendEventAsync( await iotClient.CloseAsync().ConfigureAwait(false); } } + + [ClassCleanup] + public static void CleanupCertificates() + { + s_individualEnrollmentCertificate?.Dispose(); + s_groupEnrollmentCertificate?.Dispose(); + } } } diff --git a/iothub/device/src/DeviceAuthenticationWithX509Certificate.cs b/iothub/device/src/DeviceAuthenticationWithX509Certificate.cs index b8e62fb24b..7d38987b4d 100644 --- a/iothub/device/src/DeviceAuthenticationWithX509Certificate.cs +++ b/iothub/device/src/DeviceAuthenticationWithX509Certificate.cs @@ -82,14 +82,12 @@ private void SetDeviceId(string deviceId) } /// - /// Dispose the X509 certificate associated with this authentication method. + /// The managed resource should be disposed by the user. + /// This library intentionally does not dispose it here since the user might want to + /// reuse the certificate instance elsewhere for some other operation. /// public void Dispose() { -#if !NET451 - Certificate?.Dispose(); - Certificate = null; -#endif } } } diff --git a/iothub/device/src/Microsoft.Azure.Devices.Client.csproj b/iothub/device/src/Microsoft.Azure.Devices.Client.csproj index 59f0aafab8..84170ecc0f 100644 --- a/iothub/device/src/Microsoft.Azure.Devices.Client.csproj +++ b/iothub/device/src/Microsoft.Azure.Devices.Client.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0;net472;net451 - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0;net472;net451 + net5.0;netstandard2.1;netstandard2.0 8.0 true @@ -60,6 +60,15 @@ Common + + Common + + + Common + + + Common + diff --git a/iothub/device/src/ModernDotNet/HsmAuthentication/Transport/HttpUdsMessageHandler.cs b/iothub/device/src/ModernDotNet/HsmAuthentication/Transport/HttpUdsMessageHandler.cs index b72c87d203..66a0ea0372 100644 --- a/iothub/device/src/ModernDotNet/HsmAuthentication/Transport/HttpUdsMessageHandler.cs +++ b/iothub/device/src/ModernDotNet/HsmAuthentication/Transport/HttpUdsMessageHandler.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client.HsmAuthentication.Transport { @@ -25,15 +26,11 @@ protected override async Task SendAsync(HttpRequestMessage using var stream = new HttpBufferedStream(new NetworkStream(socket, true)); byte[] requestBytes = HttpRequestResponseSerializer.SerializeRequest(request); + await stream.WriteToStreamAsync(requestBytes, cancellationToken).ConfigureAwait(false); -#if NET451 || NET472 || NETSTANDARD2_0 - await stream.WriteAsync(requestBytes, 0, requestBytes.Length, cancellationToken).ConfigureAwait(false); -#else - await stream.WriteAsync(requestBytes, cancellationToken).ConfigureAwait(false); -#endif if (request.Content != null) { - await request.Content.CopyToAsync(stream).ConfigureAwait(false); + await request.Content.CopyToStreamAsync(stream, cancellationToken).ConfigureAwait(false); } HttpResponseMessage response = await HttpRequestResponseSerializer.DeserializeResponseAsync(stream, cancellationToken).ConfigureAwait(false); diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotConnector.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotConnector.cs index 926c1ad283..863b216bda 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotConnector.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotConnector.cs @@ -49,14 +49,9 @@ public async Task OpenConnectionAsync(TimeSpan timeout) MaxFrameSize = AmqpConstants.DefaultMaxFrameSize, ContainerId = CommonResources.GetNewStringGuid(), HostName = _hostName, + IdleTimeOut = Convert.ToUInt32(_amqpTransportSettings.IdleTimeout.TotalMilliseconds), }; - TimeSpan idleTimeout = _amqpTransportSettings.IdleTimeout; - if (idleTimeout != null) - { - amqpConnectionSettings.IdleTimeOut = Convert.ToUInt32(idleTimeout.TotalMilliseconds); - } - _amqpIotTransport = new AmqpIotTransport(amqpSettings, _amqpTransportSettings, _hostName, s_disableServerCertificateValidation); TransportBase transportBase = await _amqpIotTransport.InitializeAsync(timeout).ConfigureAwait(false); diff --git a/iothub/device/src/Transport/HttpClientHelper.cs b/iothub/device/src/Transport/HttpClientHelper.cs index a35fbc9adb..363fb42841 100644 --- a/iothub/device/src/Transport/HttpClientHelper.cs +++ b/iothub/device/src/Transport/HttpClientHelper.cs @@ -579,12 +579,13 @@ private static StringContent CreateContent(T entity) private static async Task ReadAsAsync(HttpContent content, CancellationToken token) { token.ThrowIfCancellationRequested(); - using Stream stream = await content.ReadAsStreamAsync().ConfigureAwait(false); + + using Stream stream = await content.ReadHttpContentAsStream(token).ConfigureAwait(false); using var reader = new StreamReader(stream); using var jsonReader = new JsonTextReader(reader); return new JsonSerializer().Deserialize(jsonReader); } #endif - } + } } diff --git a/iothub/device/src/Transport/HttpTransportHandler.cs b/iothub/device/src/Transport/HttpTransportHandler.cs index cee9c71665..82d5e21bce 100644 --- a/iothub/device/src/Transport/HttpTransportHandler.cs +++ b/iothub/device/src/Transport/HttpTransportHandler.cs @@ -284,7 +284,7 @@ public override async Task ReceiveAsync(CancellationToken cancellationT return null; } - byte[] byteContent = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + byte[] byteContent = await responseMessage.Content.ReadHttpContentAsByteArrayAsync(cancellationToken).ConfigureAwait(false); Message message = byteContent != null ? new Message(byteContent) diff --git a/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs b/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs index 7815dd1503..3ef42f0769 100644 --- a/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs +++ b/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs @@ -14,7 +14,6 @@ using System.Threading.Tasks; using DotNetty.Buffers; using DotNetty.Codecs.Mqtt.Packets; -using DotNetty.Common.Concurrency; using DotNetty.Handlers.Tls; using DotNetty.Transport.Channels; using Microsoft.Azure.Devices.Client.Common; @@ -22,6 +21,12 @@ using Microsoft.Azure.Devices.Client.Extensions; using Microsoft.Azure.Devices.Shared; +#if NET5_0 +using TaskCompletionSource = System.Threading.Tasks.TaskCompletionSource; +#else +using TaskCompletionSource = Microsoft.Azure.Devices.Shared.TaskCompletionSource; +#endif + namespace Microsoft.Azure.Devices.Client.Transport.Mqtt { // @@ -640,7 +645,7 @@ private void ProcessSubAck(IChannelHandlerContext context, SubAckPacket packet) if (_subscribeCompletions.TryRemove(packet.PacketId, out TaskCompletionSource task)) { - task.TryComplete(); + task.TrySetResult(); } if (Logging.IsEnabled) @@ -681,7 +686,7 @@ private void ProcessUnsubAck(IChannelHandlerContext context, UnsubAckPacket pack if (_unsubscribeCompletions.TryRemove(packet.PacketId, out TaskCompletionSource task)) { - task.TryComplete(); + task.TrySetResult(); } if (Logging.IsEnabled) @@ -795,7 +800,7 @@ private Task ProcessAckAsync(IChannelHandlerContext context, PublishWorkItem pub if (Logging.IsEnabled) Logging.Enter(this, context.Name, publish?.Value, nameof(ProcessAckAsync)); - publish.Completion.Complete(); + publish.Completion.SetResult(); if (Logging.IsEnabled) Logging.Exit(this, context.Name, publish?.Value, nameof(ProcessAckAsync)); @@ -937,7 +942,7 @@ private async Task SendMessageToServerAsync(IChannelHandlerContext context, Publ await WriteMessageAsync(context, publish.Value, s_shutdownOnWriteErrorHandler).ConfigureAwait(true); if (publish.Value.QualityOfService == QualityOfService.AtMostOnce) { - publish.Completion.TryComplete(); + publish.Completion.TrySetResult(); } } catch (Exception ex) diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs index caaf8959c4..4676114371 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs @@ -21,7 +21,6 @@ using DotNetty.Buffers; using DotNetty.Codecs.Mqtt; using DotNetty.Codecs.Mqtt.Packets; -using DotNetty.Common.Concurrency; using DotNetty.Handlers.Logging; using DotNetty.Handlers.Tls; using DotNetty.Transport.Bootstrapping; @@ -33,6 +32,12 @@ using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; +#if NET5_0 +using TaskCompletionSource = System.Threading.Tasks.TaskCompletionSource; +#else +using TaskCompletionSource = Microsoft.Azure.Devices.Shared.TaskCompletionSource; +#endif + namespace Microsoft.Azure.Devices.Client.Transport.Mqtt { // @@ -509,7 +514,7 @@ public void OnConnected() { if (TryStateTransition(TransportState.Opening, TransportState.Open)) { - _connectCompletion.TryComplete(); + _connectCompletion.TrySetResult(); } } @@ -1080,7 +1085,7 @@ await _channel .ConfigureAwait(true); if (TryStateTransition(TransportState.Subscribing, TransportState.Receiving) - && _subscribeCompletionSource.TryComplete()) + && _subscribeCompletionSource.TrySetResult()) { return; } diff --git a/iothub/device/src/Transport/Mqtt/PublishWorkItem.cs b/iothub/device/src/Transport/Mqtt/PublishWorkItem.cs index 38290ff525..7415d78182 100644 --- a/iothub/device/src/Transport/Mqtt/PublishWorkItem.cs +++ b/iothub/device/src/Transport/Mqtt/PublishWorkItem.cs @@ -3,7 +3,12 @@ using System; using DotNetty.Codecs.Mqtt.Packets; -using DotNetty.Common.Concurrency; + +#if NET5_0 +using TaskCompletionSource = System.Threading.Tasks.TaskCompletionSource; +#else +using TaskCompletionSource = Microsoft.Azure.Devices.Shared.TaskCompletionSource; +#endif namespace Microsoft.Azure.Devices.Client.Transport.Mqtt { @@ -15,12 +20,12 @@ internal sealed class PublishWorkItem : ReferenceCountedObjectContainer @@ -76,7 +81,7 @@ public void Complete() switch (State) { case States.Idle: - _completionSource.TryComplete(); + _completionSource.TrySetResult(); break; case States.Processing: @@ -155,7 +160,7 @@ private async void StartWorkQueueProcessingAsync(IChannelHandlerContext context) case States.FinalProcessing: case States.Aborted: - _completionSource.TryComplete(); + _completionSource.TrySetResult(); break; default: diff --git a/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj b/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj index ea152b0a59..481fab720b 100644 --- a/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj +++ b/iothub/device/tests/Microsoft.Azure.Devices.Client.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18;net472;net451 - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18;net472;net451 + net5.0;netcoreapp3.1;netcoreapp2.1.18 8.0 False diff --git a/iothub/device/tests/RetryDelegatingHandlerImplicitOpenTests.cs b/iothub/device/tests/RetryDelegatingHandlerImplicitOpenTests.cs index e1e23885ba..c972f740d3 100644 --- a/iothub/device/tests/RetryDelegatingHandlerImplicitOpenTests.cs +++ b/iothub/device/tests/RetryDelegatingHandlerImplicitOpenTests.cs @@ -99,7 +99,7 @@ public async Task Retry_CloseAsyncSuccessDisposesHandler_Throws() public async Task OpenAsyncTwoCallersOnlyOneOpenCalled() { var contextMock = Substitute.For(); - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); var innerHandlerMock = Substitute.For(); int callCounter = 0; innerHandlerMock.OpenAsync(Arg.Any()).Returns(t => @@ -113,7 +113,7 @@ public async Task OpenAsyncTwoCallersOnlyOneOpenCalled() var cancellationToken = new CancellationToken(); Task firstOpen = sut.OpenAsync(cancellationToken); Task secondOpen = sut.OpenAsync(cancellationToken); - tcs.Complete(); + tcs.SetResult(0); await Task.WhenAll(firstOpen, secondOpen).ConfigureAwait(false); Assert.AreEqual(1, callCounter); @@ -151,7 +151,7 @@ public async Task OpenAsyncInnerFailedSutIsOpenAndCanBeReopen() public async Task OpenAsyncInnerCancelledSutIsOpenAndCanBeReopen() { var contextMock = Substitute.For(); - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); tcs.SetCanceled(); var innerHandlerMock = Substitute.For(); innerHandlerMock.OpenAsync(Arg.Any()).Returns(tcs.Task); diff --git a/iothub/service/src/Microsoft.Azure.Devices.csproj b/iothub/service/src/Microsoft.Azure.Devices.csproj index 369761f9a5..936e69ffb0 100644 --- a/iothub/service/src/Microsoft.Azure.Devices.csproj +++ b/iothub/service/src/Microsoft.Azure.Devices.csproj @@ -1,9 +1,9 @@ - - - netstandard2.1;netstandard2.0;net472;net451;netcoreapp2.1 - netstandard2.1;netstandard2.0 + + + net5.0;netstandard2.1;netstandard2.0;net472;net451;netcoreapp2.1 + net5.0;netstandard2.1;netstandard2.0 8.0 true @@ -118,6 +118,8 @@ Common\Utils.cs + + diff --git a/iothub/service/tests/Microsoft.Azure.Devices.Tests.csproj b/iothub/service/tests/Microsoft.Azure.Devices.Tests.csproj index 75461e56c3..8b572f5ee9 100644 --- a/iothub/service/tests/Microsoft.Azure.Devices.Tests.csproj +++ b/iothub/service/tests/Microsoft.Azure.Devices.Tests.csproj @@ -1,8 +1,8 @@  - netcoreapp3.1;netcoreapp2.1.18;net472;net451 - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18;net472;net451 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False 8.0 diff --git a/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj b/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj index 417e7cbd39..63a45c4a7b 100644 --- a/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj +++ b/provisioning/device/src/Microsoft.Azure.Devices.Provisioning.Client.csproj @@ -1,6 +1,6 @@ - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 8.0 true true diff --git a/provisioning/device/tests/Microsoft.Azure.Devices.Provisioning.Client.Tests.csproj b/provisioning/device/tests/Microsoft.Azure.Devices.Provisioning.Client.Tests.csproj index 2dea5afdb2..94f283b06e 100644 --- a/provisioning/device/tests/Microsoft.Azure.Devices.Provisioning.Client.Tests.csproj +++ b/provisioning/device/tests/Microsoft.Azure.Devices.Provisioning.Client.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18 8.0 False diff --git a/provisioning/service/src/Contract/ContractApiHttp.cs b/provisioning/service/src/Contract/ContractApiHttp.cs index d467032462..37b79a2595 100644 --- a/provisioning/service/src/Contract/ContractApiHttp.cs +++ b/provisioning/service/src/Contract/ContractApiHttp.cs @@ -123,7 +123,7 @@ public async Task RequestAsync( } response = new ContractApiResponse( - await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false), + await httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false), httpResponse.StatusCode, httpResponse.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault()), httpResponse.ReasonPhrase); diff --git a/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj b/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj index c8a073043c..18ec623360 100644 --- a/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj +++ b/provisioning/service/src/Microsoft.Azure.Devices.Provisioning.Service.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 8.0 true true @@ -50,6 +50,7 @@ + diff --git a/provisioning/service/tests/Config/X509CertificateWithInfoTests.cs b/provisioning/service/tests/Config/X509CertificateWithInfoTests.cs index 9e4a006952..3878712051 100644 --- a/provisioning/service/tests/Config/X509CertificateWithInfoTests.cs +++ b/provisioning/service/tests/Config/X509CertificateWithInfoTests.cs @@ -76,7 +76,7 @@ public void X509CertificateWithInfoConstructorThrowsOnNullX509Certificate() { // arrange X509Certificate2 certificateNull = null; - var certificateEmpty = new X509Certificate2(); + using var certificateEmpty = new X509Certificate2(); string certificateString = null; string certificateStringEmpty = ""; string certificateStringInvalid = @@ -109,10 +109,10 @@ public void X509CertificateWithInfoConstructorThrowsOnNullX509Certificate() public void X509CertificateWithInfoConstructorSucceedOnValidX509Certificate() { // arrange - X509Certificate2 certificate = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE)); + using var certificate = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE)); // act - X509CertificateWithInfo x509CertificateWithInfo = new X509CertificateWithInfo(certificate); + var x509CertificateWithInfo = new X509CertificateWithInfo(certificate); // assert Assert.AreEqual(PUBLIC_KEY_CERTIFICATE_STRING, x509CertificateWithInfo.Certificate); @@ -126,7 +126,7 @@ public void X509CertificateWithInfoConstructorSucceedOnValidX509CertificateStrin string certificate = PUBLIC_KEY_CERTIFICATE; // act - X509CertificateWithInfo x509CertificateWithInfo = new X509CertificateWithInfo(certificate); + var x509CertificateWithInfo = new X509CertificateWithInfo(certificate); // assert Assert.AreEqual(PUBLIC_KEY_CERTIFICATE, x509CertificateWithInfo.Certificate); @@ -143,7 +143,7 @@ public void X509CertificateWithInfoSucceedOnJsonWithInfo() string json = makeJson(SUBJECT_NAME, SHA1THUMBPRINT, SHA256THUMBPRINT, ISSUER_NAME, NOT_BEFORE_UTC_STRING, NOT_AFTER_UTC_STRING, SERIAL_NUMBER, VERSION); // act - X509CertificateWithInfo x509CertificateWithInfo = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + var x509CertificateWithInfo = Newtonsoft.Json.JsonConvert.DeserializeObject(json); // assert Assert.IsNotNull(x509CertificateWithInfo.Info); diff --git a/provisioning/service/tests/Config/X509CertificatesTests.cs b/provisioning/service/tests/Config/X509CertificatesTests.cs index 3075797cf6..3dc2f23a16 100644 --- a/provisioning/service/tests/Config/X509CertificatesTests.cs +++ b/provisioning/service/tests/Config/X509CertificatesTests.cs @@ -17,9 +17,7 @@ public class X509CertificatesTests private const string SHA256THUMBPRINT = "validEnrollmentGroupId"; private const string ISSUER_NAME = "CN=ROOT_00000000-0000-0000-0000-000000000000, OU=Azure IoT, O=MSFT, C=US"; private const string NOT_BEFORE_UTC_STRING = "2017-11-14T12:34:18.123Z"; - private DateTime NOT_BEFORE_UTC = new DateTime(2017, 11, 14, 12, 34, 18, 123, DateTimeKind.Utc); private const string NOT_AFTER_UTC_STRING = "2017-11-14T12:34:18.321Z"; - private DateTime NOT_AFTER_UTC = new DateTime(2017, 11, 14, 12, 34, 18, 321, DateTimeKind.Utc); private const string SERIAL_NUMBER = "000000000000000000"; private const int VERSION = 3; @@ -64,10 +62,10 @@ private string MakeCertInfoJson( public void X509CertificatesSucceedOnValidPrimaryX509Certificate() { // arrange - X509Certificate2 primary = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE_STRING)); + using var primary = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE_STRING)); // act - X509Certificates x509Certificates = new X509Certificates(primary); + var x509Certificates = new X509Certificates(primary); // assert Assert.IsNotNull(x509Certificates.Primary); @@ -78,11 +76,11 @@ public void X509CertificatesSucceedOnValidPrimaryX509Certificate() public void X509CertificatesSucceedOnValidPrimaryAndSecondaryX509Certificate() { // arrange - X509Certificate2 primary = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE_STRING)); - X509Certificate2 secondary = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE_STRING)); + using var primary = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE_STRING)); + using var secondary = new X509Certificate2(System.Text.Encoding.ASCII.GetBytes(PUBLIC_KEY_CERTIFICATE_STRING)); // act - X509Certificates x509Certificates = new X509Certificates(primary, secondary); + var x509Certificates = new X509Certificates(primary, secondary); // assert Assert.IsNotNull(x509Certificates.Primary); @@ -96,7 +94,7 @@ public void X509CertificatesSucceedOnValidPrimaryString() string primary = PUBLIC_KEY_CERTIFICATE_STRING; // act - X509Certificates x509Certificates = new X509Certificates(primary); + var x509Certificates = new X509Certificates(primary); // assert Assert.IsNotNull(x509Certificates.Primary); @@ -111,7 +109,7 @@ public void X509CertificatesSucceedOnValidPrimaryAndSecondaryX509CertificateWith string secondary = PUBLIC_KEY_CERTIFICATE_STRING; // act - X509Certificates x509Certificates = new X509Certificates(primary, secondary); + var x509Certificates = new X509Certificates(primary, secondary); // assert Assert.IsNotNull(x509Certificates.Primary); @@ -129,7 +127,7 @@ public void X509CertificatesSucceedOnJsonWithPrimaryCertificate() "}"; // act - X509Certificates x509Certificates = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + var x509Certificates = JsonConvert.DeserializeObject(json); // assert Assert.IsNotNull(x509Certificates.Primary); @@ -150,7 +148,7 @@ public void X509CertificatesSucceedOnJsonWithPrimaryAndSecondaryCertificate() "}"; // act - X509Certificates x509Certificates = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + var x509Certificates = JsonConvert.DeserializeObject(json); // assert Assert.IsNotNull(x509Certificates.Primary); @@ -194,7 +192,7 @@ public void X509CertificatesThrowsOnJsonWithoutPrimaryCertificate() "}"; // act - assert - TestAssert.Throws(() => Newtonsoft.Json.JsonConvert.DeserializeObject(json)); + TestAssert.Throws(() => JsonConvert.DeserializeObject(json)); } } } diff --git a/provisioning/service/tests/Microsoft.Azure.Devices.Provisioning.Service.Tests.csproj b/provisioning/service/tests/Microsoft.Azure.Devices.Provisioning.Service.Tests.csproj index 34927fc0bf..178c417cd0 100644 --- a/provisioning/service/tests/Microsoft.Azure.Devices.Provisioning.Service.Tests.csproj +++ b/provisioning/service/tests/Microsoft.Azure.Devices.Provisioning.Service.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False 8.0 diff --git a/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj b/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj index c7782439a8..714d5048dc 100644 --- a/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj +++ b/provisioning/transport/amqp/src/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 8.0 true Microsoft.Azure.Devices.Provisioning.Client.Transport diff --git a/provisioning/transport/amqp/tests/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.Tests.csproj b/provisioning/transport/amqp/tests/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.Tests.csproj index a16273abb1..16d9df9666 100644 --- a/provisioning/transport/amqp/tests/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.Tests.csproj +++ b/provisioning/transport/amqp/tests/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False 8.0 diff --git a/provisioning/transport/amqp/tests/ProvisioningErrorDetailsAmqpTests.cs b/provisioning/transport/amqp/tests/ProvisioningErrorDetailsAmqpTests.cs index 7fafca76bf..ba0a6e39fc 100644 --- a/provisioning/transport/amqp/tests/ProvisioningErrorDetailsAmqpTests.cs +++ b/provisioning/transport/amqp/tests/ProvisioningErrorDetailsAmqpTests.cs @@ -1,14 +1,12 @@ // 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 Microsoft.Azure.Amqp; using Microsoft.Azure.Amqp.Encoding; using Microsoft.Azure.Amqp.Framing; using Microsoft.Azure.Devices.Provisioning.Client.Transport; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.Azure.Devices.Provisioning.Transport.Amqp.UnitTests { @@ -16,18 +14,22 @@ namespace Microsoft.Azure.Devices.Provisioning.Transport.Amqp.UnitTests [TestCategory("Unit")] public class ProvisioningErrorDetailsAmqpTests { - private static TimeSpan defaultInterval = TimeSpan.FromSeconds(2); + private static readonly TimeSpan s_defaultInterval = TimeSpan.FromSeconds(2); [TestMethod] public void GetRetryFromRejectedSuccess() { int expectedSeconds = 32; - Rejected Rejected = new Rejected(); - Rejected.Error = new Error(); - Rejected.Error.Info = new Fields(); - Rejected.Error.Info.Add(new AmqpSymbol("Retry-After"), expectedSeconds); + var rejected = new Rejected + { + Error = new Error() + }; + rejected.Error.Info = new Fields + { + { new AmqpSymbol("Retry-After"), expectedSeconds } + }; - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(Rejected, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultInterval); Assert.IsNotNull(actual); Assert.AreEqual(actual?.Seconds, expectedSeconds); @@ -37,40 +39,50 @@ public void GetRetryFromRejectedSuccess() public void GetRetryFromRejectedFallsBackToDefaultIfNegativeRetryAfterProvided() { int expectedSeconds = -1; - Rejected rejected = new Rejected(); - rejected.Error = new Error(); - rejected.Error.Info = new Fields(); - rejected.Error.Info.Add(new Azure.Amqp.Encoding.AmqpSymbol("Retry-After"), expectedSeconds); + var rejected = new Rejected + { + Error = new Error() + }; + rejected.Error.Info = new Fields + { + { new AmqpSymbol("Retry-After"), expectedSeconds } + }; - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultInterval); Assert.IsNotNull(actual); - Assert.AreEqual(actual?.Seconds, defaultInterval.Seconds); + Assert.AreEqual(actual?.Seconds, s_defaultInterval.Seconds); } [TestMethod] public void GetRetryFromRejectedFallsBackToDefaultIfRetryAfterProvidedIs0() { int expectedSeconds = 0; - Rejected rejected = new Rejected(); - rejected.Error = new Error(); - rejected.Error.Info = new Fields(); - rejected.Error.Info.Add(new Azure.Amqp.Encoding.AmqpSymbol("Retry-After"), expectedSeconds); + var rejected = new Rejected + { + Error = new Error() + }; + rejected.Error.Info = new Fields + { + { new AmqpSymbol("Retry-After"), expectedSeconds } + }; - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultInterval); Assert.IsNotNull(actual); - Assert.AreEqual(actual?.Seconds, defaultInterval.Seconds); + Assert.AreEqual(actual?.Seconds, s_defaultInterval.Seconds); } [TestMethod] public void GetRetryFromRejectedReturnsNullIfNoErrorInfoEntries() { - Rejected rejected = new Rejected(); - rejected.Error = new Error(); + var rejected = new Rejected + { + Error = new Error() + }; rejected.Error.Info = new Fields(); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultInterval); Assert.IsNull(actual); } @@ -78,10 +90,12 @@ public void GetRetryFromRejectedReturnsNullIfNoErrorInfoEntries() [TestMethod] public void GetRetryFromRejectedReturnsNullIfNoErrorInfo() { - Rejected rejected = new Rejected(); - rejected.Error = new Error(); + var rejected = new Rejected + { + Error = new Error() + }; - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultInterval); Assert.IsNull(actual); } @@ -89,9 +103,9 @@ public void GetRetryFromRejectedReturnsNullIfNoErrorInfo() [TestMethod] public void GetRetryFromRejectedReturnsNullIfNoError() { - Rejected rejected = new Rejected(); + var rejected = new Rejected(); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromRejection(rejected, s_defaultInterval); Assert.IsNull(actual); } @@ -100,10 +114,10 @@ public void GetRetryFromRejectedReturnsNullIfNoError() public void GetRetryAfterFromApplicationPropertiesSuccess() { int expectedRetryAfter = 42; - AmqpMessage amqpResponse = AmqpMessage.Create(); + using AmqpMessage amqpResponse = AmqpMessage.Create(); amqpResponse.ApplicationProperties = new ApplicationProperties(); amqpResponse.ApplicationProperties.Map.Add(new MapKey("Retry-After"), expectedRetryAfter); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultInterval); Assert.IsNotNull(actual); Assert.AreEqual(expectedRetryAfter, actual?.Seconds); } @@ -112,40 +126,40 @@ public void GetRetryAfterFromApplicationPropertiesSuccess() public void GetRetryAfterFromApplicationPropertiesReturnsDefaultIfRetryAfterValueIsNegative() { int expectedRetryAfter = -1; - AmqpMessage amqpResponse = AmqpMessage.Create(); + using AmqpMessage amqpResponse = AmqpMessage.Create(); amqpResponse.ApplicationProperties = new ApplicationProperties(); amqpResponse.ApplicationProperties.Map.Add(new MapKey("Retry-After"), expectedRetryAfter); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultInterval); Assert.IsNotNull(actual); - Assert.AreEqual(defaultInterval.Seconds, actual?.Seconds); + Assert.AreEqual(s_defaultInterval.Seconds, actual?.Seconds); } [TestMethod] public void GetRetryAfterFromApplicationPropertiesReturnsDefaultIfRetryAfterValueIsZero() { int expectedRetryAfter = 0; - AmqpMessage amqpResponse = AmqpMessage.Create(); + using AmqpMessage amqpResponse = AmqpMessage.Create(); amqpResponse.ApplicationProperties = new ApplicationProperties(); amqpResponse.ApplicationProperties.Map.Add(new MapKey("Retry-After"), expectedRetryAfter); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultInterval); Assert.IsNotNull(actual); - Assert.AreEqual(defaultInterval.Seconds, actual?.Seconds); + Assert.AreEqual(s_defaultInterval.Seconds, actual?.Seconds); } [TestMethod] public void GetRetryAfterFromApplicationPropertiesReturnsNullIfNoRetryAfterApplicationProperty() { - AmqpMessage amqpResponse = AmqpMessage.Create(); + using AmqpMessage amqpResponse = AmqpMessage.Create(); amqpResponse.ApplicationProperties = new ApplicationProperties(); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, defaultInterval); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultInterval); Assert.IsNull(actual); } [TestMethod] public void GetRetryAfterFromApplicationPropertiesReturnsNullIfNoApplicationProperties() { - AmqpMessage amqpResponse = AmqpMessage.Create(); - TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, defaultInterval); + using AmqpMessage amqpResponse = AmqpMessage.Create(); + TimeSpan? actual = ProvisioningErrorDetailsAmqp.GetRetryAfterFromApplicationProperties(amqpResponse, s_defaultInterval); Assert.IsNull(actual); } } diff --git a/provisioning/transport/http/src/Generated/RuntimeRegistration.cs b/provisioning/transport/http/src/Generated/RuntimeRegistration.cs index cb159c2c58..954f915718 100644 --- a/provisioning/transport/http/src/Generated/RuntimeRegistration.cs +++ b/provisioning/transport/http/src/Generated/RuntimeRegistration.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Azure.Devices.Provisioning.Client.Transport.Models; +using Microsoft.Azure.Devices.Shared; using Microsoft.Rest; using Microsoft.Rest.Serialization; using Newtonsoft.Json; @@ -170,7 +171,7 @@ public async Task> OperationS var ex = new HttpOperationException(string.Format(CultureInfo.InvariantCulture, "Operation returned an invalid status code '{0}'", _statusCode)); if (_httpResponse.Content != null) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); } else { @@ -198,7 +199,7 @@ public async Task> OperationS // Deserialize Response if ((int)_statusCode == 200) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); try { _result.Body = SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); @@ -216,7 +217,7 @@ public async Task> OperationS // Deserialize Response if ((int)_statusCode == 202) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); try { _result.Body = SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); @@ -364,7 +365,7 @@ public async Task> OperationS var ex = new HttpOperationException(string.Format(CultureInfo.InvariantCulture, "Operation returned an invalid status code '{0}'", _statusCode)); if (_httpResponse.Content != null) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); } else { @@ -392,7 +393,7 @@ public async Task> OperationS // Deserialize Response if ((int)_statusCode == 200) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); try { _result.Body = SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); @@ -558,7 +559,7 @@ public async Task> RegisterDe var ex = new HttpOperationException(string.Format(CultureInfo.InvariantCulture, "Operation returned an invalid status code '{0}'", _statusCode)); if (_httpResponse.Content != null) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); } else { @@ -586,7 +587,7 @@ public async Task> RegisterDe // Deserialize Response if ((int)_statusCode == 202) { - _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + _responseContent = await _httpResponse.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); try { _result.Body = SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); diff --git a/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj b/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj index e07f658276..bc77d11969 100644 --- a/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj +++ b/provisioning/transport/http/src/Microsoft.Azure.Devices.Provisioning.Transport.Http.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 8.0 true Microsoft.Azure.Devices.Provisioning.Client.Transport @@ -83,6 +83,9 @@ Generated\Models\DeviceRegistration.cs + + Common + diff --git a/provisioning/transport/http/src/TPM/TpmCredentials.cs b/provisioning/transport/http/src/TPM/TpmCredentials.cs index 9ea5530de4..ea5e8ab97a 100644 --- a/provisioning/transport/http/src/TPM/TpmCredentials.cs +++ b/provisioning/transport/http/src/TPM/TpmCredentials.cs @@ -33,7 +33,13 @@ public override Task ProcessHttpRequestAsync(HttpRequestMessage request, Cancell SetAuthorizationHeader(request, _sasToken); }; +#if NET5_0 + HttpRequestOptions requestOptions = request.Options; + var requestOptionsKey = new HttpRequestOptionsKey>(TpmDelegatingHandler.ProvisioningHeaderName); + requestOptions.Set(requestOptionsKey, action); +#else request.Properties.Add(TpmDelegatingHandler.ProvisioningHeaderName, action); +#endif } else { diff --git a/provisioning/transport/http/src/TPM/TpmDelegatingHandler.cs b/provisioning/transport/http/src/TPM/TpmDelegatingHandler.cs index bb82a85a57..008351673e 100644 --- a/provisioning/transport/http/src/TPM/TpmDelegatingHandler.cs +++ b/provisioning/transport/http/src/TPM/TpmDelegatingHandler.cs @@ -1,16 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.Azure.Devices.Shared; -using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Globalization; using System.Net; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Devices.Shared; +using Newtonsoft.Json; namespace Microsoft.Azure.Devices.Provisioning.Client.Transport { @@ -37,12 +34,16 @@ protected override async Task SendAsync( if(response.StatusCode == HttpStatusCode.Unauthorized) { +#if NET5_0 + if (request.Options.TryGetValue(new HttpRequestOptionsKey(ProvisioningHeaderName), out object result)) +#else if (request.Properties.TryGetValue(ProvisioningHeaderName, out object result)) +#endif { if (result is Action setSasToken) { string target = GetTarget(request.RequestUri.LocalPath); - string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + string responseContent = await response.Content.ReadHttpContentAsStringAsync(cancellationToken).ConfigureAwait(false); TpmChallenge challenge = JsonConvert.DeserializeObject(responseContent); string sasToken = ProvisioningSasBuilder.ExtractServiceAuthKey( diff --git a/provisioning/transport/http/tests/Microsoft.Azure.Devices.Provisioning.Transport.Http.Tests.csproj b/provisioning/transport/http/tests/Microsoft.Azure.Devices.Provisioning.Transport.Http.Tests.csproj index df36da49dd..5dca5a9d01 100644 --- a/provisioning/transport/http/tests/Microsoft.Azure.Devices.Provisioning.Transport.Http.Tests.csproj +++ b/provisioning/transport/http/tests/Microsoft.Azure.Devices.Provisioning.Transport.Http.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False 8.0 diff --git a/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj b/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj index 5f2e0d7c6c..09a0aa739a 100644 --- a/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj +++ b/provisioning/transport/mqtt/src/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 8.0 true Microsoft.Azure.Devices.Provisioning.Client.Transport diff --git a/provisioning/transport/mqtt/tests/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.Tests.csproj b/provisioning/transport/mqtt/tests/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.Tests.csproj index 01ba2ddb59..ca2c8374e4 100644 --- a/provisioning/transport/mqtt/tests/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.Tests.csproj +++ b/provisioning/transport/mqtt/tests/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False 8.0 diff --git a/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.cs b/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.cs index 21b6e37238..7ae1568000 100644 --- a/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.cs +++ b/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.cs @@ -23,21 +23,22 @@ public class SecurityProviderTpmSimulator : SecurityProviderTpm private const int SimulatorPort = 2321; private const int TcpTpmDeviceTimeoutSeconds = 30; + private TcpTpmDevice _tpmDevice; private SecurityProviderTpmHsm _innerClient; public SecurityProviderTpmSimulator(string registrationId) : base(registrationId) { - var tpmDevice = new TcpTpmDevice(SimulatorAddress, SimulatorPort); - tpmDevice.Connect(); - tpmDevice.SetSocketTimeout(TcpTpmDeviceTimeoutSeconds); - tpmDevice.PowerCycle(); + _tpmDevice = new TcpTpmDevice(SimulatorAddress, SimulatorPort); + _tpmDevice.Connect(); + _tpmDevice.SetSocketTimeout(TcpTpmDeviceTimeoutSeconds); + _tpmDevice.PowerCycle(); - using (var tpm2 = new Tpm2(tpmDevice)) + using (var tpm2 = new Tpm2(_tpmDevice)) { tpm2.Startup(Su.Clear); } - _innerClient = new SecurityProviderTpmHsm(GetRegistrationID(), tpmDevice); + _innerClient = new SecurityProviderTpmHsm(GetRegistrationID(), _tpmDevice); } public override void ActivateIdentityKey(byte[] encryptedKey) @@ -62,7 +63,7 @@ public override byte[] Sign(byte[] data) public static void StopSimulatorProcess() { - foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(SimulatorExeName))) + foreach (Process process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(SimulatorExeName))) { try { @@ -106,7 +107,7 @@ public static void StartSimulatorProcess() throw new InvalidOperationException($"TPM Simulator not found : {SimulatorExeName}"); } - var simulatorProcess = new Process + using var simulatorProcess = new Process { StartInfo = { @@ -123,7 +124,11 @@ protected override void Dispose(bool disposing) { if (disposing) { - _innerClient.Dispose(); + _innerClient?.Dispose(); + _innerClient = null; + + _tpmDevice?.Dispose(); + _tpmDevice = null; } } } diff --git a/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.csproj b/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.csproj index eae5928360..c838395cb4 100644 --- a/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.csproj +++ b/security/tpm/samples/SecurityProviderTpmSimulator/SecurityProviderTpmSimulator.csproj @@ -1,7 +1,8 @@ - netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 + 8.0 true Microsoft.Azure.Devices.Provisioning.Security diff --git a/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj b/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj index fc4af673a6..5c1ce60c9d 100644 --- a/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj +++ b/security/tpm/src/Microsoft.Azure.Devices.Provisioning.Security.Tpm.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0 8.0 true true diff --git a/security/tpm/tests/Microsoft.Azure.Devices.Provisioning.Security.Tpm.Tests.csproj b/security/tpm/tests/Microsoft.Azure.Devices.Provisioning.Security.Tpm.Tests.csproj index 283baacb03..93a1fdd96e 100644 --- a/security/tpm/tests/Microsoft.Azure.Devices.Provisioning.Security.Tpm.Tests.csproj +++ b/security/tpm/tests/Microsoft.Azure.Devices.Provisioning.Security.Tpm.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False 8.0 diff --git a/shared/src/Microsoft.Azure.Devices.Shared.csproj b/shared/src/Microsoft.Azure.Devices.Shared.csproj index 72d1b1eaaa..12cc00dace 100644 --- a/shared/src/Microsoft.Azure.Devices.Shared.csproj +++ b/shared/src/Microsoft.Azure.Devices.Shared.csproj @@ -1,7 +1,7 @@ - netstandard2.1;netstandard2.0;net472;net451;netcoreapp2.1 - netstandard2.1;netstandard2.0 + net5.0;netstandard2.1;netstandard2.0;net472;net451;netcoreapp2.1 + net5.0;netstandard2.1;netstandard2.0 8.0 true true diff --git a/shared/tests/Microsoft.Azure.Devices.Shared.Tests.csproj b/shared/tests/Microsoft.Azure.Devices.Shared.Tests.csproj index e96e79c80a..3635d21a6a 100644 --- a/shared/tests/Microsoft.Azure.Devices.Shared.Tests.csproj +++ b/shared/tests/Microsoft.Azure.Devices.Shared.Tests.csproj @@ -1,8 +1,8 @@  Microsoft.Azure.Devices.Shared.Tests - netcoreapp3.1;netcoreapp2.1.18;net472;net451 - netcoreapp3.1;netcoreapp2.1.18 + net5.0;netcoreapp3.1;netcoreapp2.1.18;net472;net451 + net5.0;netcoreapp3.1;netcoreapp2.1.18 False diff --git a/supported_platforms.md b/supported_platforms.md index 0944a25974..bdfcd0d06b 100644 --- a/supported_platforms.md +++ b/supported_platforms.md @@ -1,11 +1,11 @@ # Microsoft Azure IoT SDKs for .NET -This SDK is tested nightly on a mix of .NET implementations on both Windows 10 and on Ubuntu 1604. For additional details -for each tested platform, see the respective sections below. +This SDK is tested nightly on a mix of .NET implementations on both Windows 10 and on Ubuntu 20.04. For additional details for each tested platform, see the respective sections below. ## Supported .NET versions The NuGet packages provide support for the following .NET versions: +- .NET 5.0 - .NET Standard 2.1 - .NET Standard 2.0 - .NET Framework 4.7.2 (IoT Hub SDKs only) @@ -23,6 +23,7 @@ Note that, while we only directly test on Windows 10, we do support other Window Nightly test platform details: .NET versions tested on +- .NET 5.0 - .NET Core 3.1 - .NET Core 2.1.18 - .NET Framework 4.7.2 (only IoT Hub SDKs tested) @@ -31,25 +32,25 @@ Nightly test platform details: Default locale: en_US, platform encoding: Cp1252 -OS name: "windows server 2016", version: "10.0", arch: "amd64", family: "windows" +OS name: "windows server 2019", version: "10.0", arch: "amd64", family: "windows" -## Ubuntu 1604 +## Ubuntu 20.04 -Note that, while we only directly test on Ubuntu 1604, we do generally support other [Linux distributions supported by .NET core](https://docs.microsoft.com/en-us/dotnet/core/install/linux). +Note that, while we only directly test on Ubuntu 20.04, we do generally support other [Linux distributions supported by .NET core](https://docs.microsoft.com/en-us/dotnet/core/install/linux). Nightly test platform details: .NET versions tested on: +- .NET 5.0 - .NET Core 3.1 - .NET Core 2.1.18 Default locale: en_US, platform encoding: UTF-8 -OS name: "linux", version: "4.15.0-1113-azure", arch: "amd64", family: "unix" +OS name: "linux", version: "5.8.0-1040-azure", arch: "amd64", family: "unix" ## Miscellaneous support notes -- This library has a [preview version](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/preview_2021-6-8) that supports .NET 5.0, but we don't officially support it in our main releases yet. - This library does not officially support being run on MacOS. - This library does not officially support being run in Xamarin applications. - .NET Standard 1.3 (IoT Hub SDKs only) is last supported in the [2020-02-27](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/2020-2-27) and in the [2020-1-31 LTS](https://github.com/Azure/azure-iot-sdk-csharp/releases/tag/lts_2020-1-31) releases. diff --git a/tools/TLS Protocols Tests/TLS protocol tests.csproj b/tools/TLS Protocols Tests/TLS protocol tests.csproj index c7da7b5c5f..04388616aa 100644 --- a/tools/TLS Protocols Tests/TLS protocol tests.csproj +++ b/tools/TLS Protocols Tests/TLS protocol tests.csproj @@ -2,8 +2,8 @@ Exe - netcoreapp2.1;net451 - netcoreapp2.1 + net5.0;netcoreapp2.1;net451 + net5.0;netcoreapp2.1 TlsProtocolTests TlsProtocolTests false diff --git a/vsts/test-release-nuget.yaml b/vsts/test-release-nuget.yaml index 2d0fadce54..ccc90b5c1b 100644 --- a/vsts/test-release-nuget.yaml +++ b/vsts/test-release-nuget.yaml @@ -11,6 +11,8 @@ jobs: # Change maxParallel to 1 make builds run in serial rather than in parallel maxParallel: 100 matrix: + .Net 5.0: + FRAMEWORK: net5.0 .Net Core 3.1: FRAMEWORK: netcoreapp3.1 .Net Core 2.1.18: @@ -18,7 +20,7 @@ jobs: condition: succeeded() pool: - name: Hosted Ubuntu 1604 + vmImage: ubuntu-20.04 steps: - task: DownloadBuildArtifacts@0 inputs: @@ -128,6 +130,8 @@ jobs: # Change maxParallel to 1 make builds run in serial rather than in parallel maxParallel: 100 matrix: + .Net 5.0: + FRAMEWORK: net5.0 .Net Core 3.1: FRAMEWORK: netcoreapp3.1 .Net Core 2.1.18: @@ -139,7 +143,7 @@ jobs: condition: succeeded() pool: - name: Hosted VS2017 + vmImage: windows-2019 steps: - task: DownloadBuildArtifacts@0 inputs: @@ -159,7 +163,7 @@ jobs: OverWrite: true - script: | - call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" + call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" sn -Vr *,31bf3856ad364e35 displayName: 'Disable strong name validation' diff --git a/vsts/vsts.yaml b/vsts/vsts.yaml index e79cf77d10..6d2c65c4c8 100644 --- a/vsts/vsts.yaml +++ b/vsts/vsts.yaml @@ -25,6 +25,8 @@ jobs: # Change maxParallel to 1 make builds run in serial rather than in parallel maxParallel: 100 matrix: + .Net 5.0: + FRAMEWORK: net5.0 .Net Core 3.1: FRAMEWORK: netcoreapp3.1 .Net Core 2.1.18: @@ -33,7 +35,7 @@ jobs: condition: succeeded() pool: # If this is changed, don't forget to update supported_platforms.md in the root directory. That document outlines what OS we test on and should stay up to date. - name: Hosted Ubuntu 1604 + vmImage: ubuntu-20.04 steps: - task: Docker@1 displayName: "Start TPM Simulator" @@ -131,6 +133,8 @@ jobs: # Change maxParallel to 1 make builds run in serial rather than in parallel maxParallel: 100 matrix: + .Net 5.0: + FRAMEWORK: net5.0 .Net Core 3.1: FRAMEWORK: netcoreapp3.1 .Net Core 2.1.18: @@ -143,10 +147,10 @@ jobs: condition: succeeded() pool: # If this is changed, don't forget to update supported_platforms.md in the root directory. That document outlines what OS we test on and should stay up to date. - name: Hosted VS2017 + vmImage: windows-2019 steps: - script: | - call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" + call "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\Common7\\Tools\\VsDevCmd.bat" sn -Vr *,31bf3856ad364e35 displayName: "Disable strong name validation" @@ -241,8 +245,7 @@ jobs: condition: succeeded() pool: - name: Azure Pipelines - vmImage: vs2017-win2016 + vmImage: windows-2019 steps: - script: | rem Run dotnet first experience. From 0be2fe7839043b8a359a375184116bda2d6cdea7 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 20 Sep 2021 15:38:31 -0700 Subject: [PATCH 67/77] fix(iot-device): Update PayloadCollection to accept dictionary of values (#2171) --- iothub/device/src/ClientPropertyCollection.cs | 66 ++- iothub/device/src/TelemetryCollection.cs | 39 ++ .../tests/ClientPropertyCollectionTests.cs | 400 ++++++++++++++++++ .../device/tests/TelemetryCollectionTests.cs | 161 +++++++ 4 files changed, 658 insertions(+), 8 deletions(-) create mode 100644 iothub/device/tests/TelemetryCollectionTests.cs diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 17729ad2c0..f624c38c30 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -44,8 +44,16 @@ public void AddRootProperty(string propertyName, object propertyValue) /// The component with the property to add. /// The name of the property to add. /// The value of the property to add. + /// or is null. public void AddComponentProperty(string componentName, string propertyName, object propertyValue) - => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, false); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, false); + } /// /// @@ -55,8 +63,16 @@ public void AddComponentProperty(string componentName, string propertyName, obje /// The component with the properties to add. /// A collection of properties to add. /// A property name in already exists in the collection. + /// or a property name in is null. public void AddComponentProperties(string componentName, IDictionary properties) - => AddInternal(properties, componentName, true); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(properties, componentName, false); + } /// /// @@ -100,9 +116,16 @@ public void AddOrUpdateRootProperty(string propertyName, object propertyValue) /// The component with the property to add or update. /// The name of the property to add or update. /// The value of the property to add or update. - /// is null. + /// or is null. public void AddOrUpdateComponentProperty(string componentName, string propertyName, object propertyValue) - => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); + } /// /// @@ -116,8 +139,16 @@ public void AddOrUpdateComponentProperty(string componentName, string propertyNa /// /// The component with the properties to add or update. /// A collection of properties to add or update. + /// or a property name in is null. public void AddOrUpdateComponentProperties(string componentName, IDictionary properties) - => AddInternal(properties, componentName, true); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(properties, componentName, true); + } /// /// @@ -133,9 +164,16 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionaryA collection of properties to add or update. /// is null. public void AddOrUpdateRootProperties(IDictionary properties) - => properties + { + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + + properties .ToList() .ForEach(entry => Collection[entry.Key] = entry.Value); + } /// /// Determines whether the specified property is present. @@ -426,14 +464,14 @@ internal static ClientPropertyCollection FromClientPropertiesAsDictionary(IDicti /// The component with the properties to add or update. /// Forces the collection to use the Add or Update behavior. /// Setting to true will simply overwrite the value. Setting to false will use - /// is null for a top-level property operation. + /// is null. private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) { // If the componentName is null then simply add the key-value pair to Collection dictionary. // This will either insert a property or overwrite it if it already exists. if (componentName == null) { - // If both the component name and properties collection are null then throw a ArgumentNullException. + // If both the component name and properties collection are null then throw an ArgumentNullException. // This is not a valid use-case. if (properties == null) { @@ -442,6 +480,12 @@ private void AddInternal(IDictionary properties, string componen foreach (KeyValuePair entry in properties) { + // A null property key is not allowed. Throw an ArgumentNullException. + if (entry.Key == null) + { + throw new ArgumentNullException(nameof(entry.Key)); + } + if (forceUpdate) { Collection[entry.Key] = entry.Value; @@ -471,6 +515,12 @@ private void AddInternal(IDictionary properties, string componen foreach (KeyValuePair entry in properties) { + // A null property key is not allowed. Throw an ArgumentNullException. + if (entry.Key == null) + { + throw new ArgumentNullException(nameof(entry.Key)); + } + if (forceUpdate) { componentProperties[entry.Key] = entry.Value; diff --git a/iothub/device/src/TelemetryCollection.cs b/iothub/device/src/TelemetryCollection.cs index 99d0164aff..321bdbec7a 100644 --- a/iothub/device/src/TelemetryCollection.cs +++ b/iothub/device/src/TelemetryCollection.cs @@ -2,6 +2,8 @@ // 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; namespace Microsoft.Azure.Devices.Client { @@ -32,5 +34,42 @@ public override void AddOrUpdate(string telemetryName, object telemetryValue) { base.AddOrUpdate(telemetryName, telemetryValue); } + + /// + /// Adds the telemetry values to the telemetry collection. + /// + /// + /// + /// An element with the same key already exists in the collection. + /// is null. + public void Add(IDictionary telemetryValues) + { + if (telemetryValues == null) + { + throw new ArgumentNullException(nameof(telemetryValues)); + } + + telemetryValues + .ToList() + .ForEach(entry => base.Add(entry.Key, entry.Value)); + } + + /// + /// Adds or updates the telemetry values in the telemetry collection. + /// + /// + /// + /// is null. + public void AddOrUpdate(IDictionary telemetryValues) + { + if (telemetryValues == null) + { + throw new ArgumentNullException(nameof(telemetryValues)); + } + + telemetryValues + .ToList() + .ForEach(entry => base.AddOrUpdate(entry.Key, entry.Value)); + } } } diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index 48e3cc6e95..bd0d1dddd4 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Microsoft.Azure.Devices.Shared; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; namespace Microsoft.Azure.Devices.Client.Test { @@ -427,6 +428,405 @@ public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIf isValueRetrieved.Should().BeFalse(); propertyValue.Should().Be(default); } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddRootProperty(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateRootProperty(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyValueSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddRootProperty("abc", null); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("abc", out object propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyValueSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddOrUpdateRootProperty("abc", null); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("abc", out object propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddPropertyValueAlreadyExistsThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + + // act + Action testAction = () => testPropertyCollection.AddRootProperty("abc", 1); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdatePropertyValueAlreadyExistsSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + + // act + testPropertyCollection.AddOrUpdateRootProperty("abc", 1); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("abc", out int propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().Be(1); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullClientPropertyCollectionThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddRootProperties(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullClientPropertyCollectionThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateRootProperties(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddClientPropertyCollectionAlreadyExistsThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + Action testAction = () => testPropertyCollection.AddRootProperties(propertyValues); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateClientPropertyCollectionAlreadyExistsSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + testPropertyCollection.AddOrUpdateRootProperties(propertyValues); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("qwe", out int value1Retrieved); + isValue1Retrieved.Should().BeTrue(); + value1Retrieved.Should().Be(98); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("abc", out int value2Retrieved); + isValue2Retrieved.Should().BeTrue(); + value2Retrieved.Should().Be(2); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyNameWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddComponentProperty("testComponent", null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyNameWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateComponentProperty("testComponent", null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullComponentNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddComponentProperty(null, "abc", 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullComponentNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateComponentProperty(null, "abc", 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyValueWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 123); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddComponentProperty("testComponent", "abc", null); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property1Value); + isValue1Retrieved.Should().BeTrue(); + property1Value.Should().Be(123); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out object property2Value); + isValue2Retrieved.Should().BeTrue(); + property2Value.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyValueWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 123); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddOrUpdateComponentProperty("testComponent", "abc", null); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property1Value); + isValue1Retrieved.Should().BeTrue(); + property1Value.Should().Be(123); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out object property2Value); + isValue2Retrieved.Should().BeTrue(); + property2Value.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullClientPropertyCollectionWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.Convention = DefaultPayloadConvention.Instance; + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 98); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove component "testComponent" from its properties. + testPropertyCollection.AddComponentProperties("testComponent", null); + + // assert + bool iscomponentValueRetrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property2Value); + iscomponentValueRetrieved.Should().BeFalse(); + + bool iscomponentRetrieved = testPropertyCollection.TryGetValue("testComponent", out object componentValue); + iscomponentRetrieved.Should().BeTrue(); + componentValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullClientPropertyCollectionWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.Convention = DefaultPayloadConvention.Instance; + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 98); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove component "testComponent" from its properties. + testPropertyCollection.AddOrUpdateComponentProperties("testComponent", null); + + // assert + bool iscomponentValueRetrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property2Value); + iscomponentValueRetrieved.Should().BeFalse(); + + bool iscomponentRetrieved = testPropertyCollection.TryGetValue("testComponent", out object componentValue); + iscomponentRetrieved.Should().BeTrue(); + componentValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddPropertyValueAlreadyExistsWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + + // act + Action testAction = () => testPropertyCollection.AddComponentProperty("testComponent", "abc", 1); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdatePropertyValueAlreadyExistsWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + + // act + testPropertyCollection.AddOrUpdateComponentProperty("testComponent", "abc", 1); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out int propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().Be(1); + } + + [TestMethod] + public void ClientPropertyCollection_AddClientPropertyCollectionAlreadyExistsWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + Action testAction = () => testPropertyCollection.AddComponentProperties("testComponent", propertyValues); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateClientPropertyCollectionAlreadyExistsWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + testPropertyCollection.AddOrUpdateComponentProperties("testComponent", propertyValues); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int value1Retrieved); + isValue1Retrieved.Should().BeTrue(); + value1Retrieved.Should().Be(98); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out int value2Retrieved); + isValue2Retrieved.Should().BeTrue(); + value2Retrieved.Should().Be(2); + } + + [TestMethod] + public void ClientPropertyCollect_AddRawClassSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + var propertyValues = new CustomClientProperty + { + Id = 12, + Name = "testProperty" + }; + var propertyValuesAsDictionary = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(propertyValues)); + + // act + testPropertyCollection.AddRootProperties(propertyValuesAsDictionary); + + // assert + bool isIdPresent = testPropertyCollection.TryGetValue("Id", out int id); + isIdPresent.Should().BeTrue(); + id.Should().Be(12); + + bool isNamePresent = testPropertyCollection.TryGetValue("Name", out string name); + isNamePresent.Should().BeTrue(); + name.Should().Be("testProperty"); + } } internal class CustomClientProperty diff --git a/iothub/device/tests/TelemetryCollectionTests.cs b/iothub/device/tests/TelemetryCollectionTests.cs new file mode 100644 index 0000000000..c27f864fcb --- /dev/null +++ b/iothub/device/tests/TelemetryCollectionTests.cs @@ -0,0 +1,161 @@ +// 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.Text; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.Client.Test +{ + [TestClass] + [TestCategory("Unit")] + public class TelemetryCollectionTests + { + [TestMethod] + public void AddNullTelemetryNameThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.Add(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateNullTelemetryNameThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.AddOrUpdate(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddNullTelemetryValueSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + testTelemetryCollection.Add("abc", null); + + // assert + testTelemetryCollection["abc"].Should().BeNull(); + } + + [TestMethod] + public void AddOrUpdateNullTelemetryValueSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + testTelemetryCollection.AddOrUpdate("abc", null); + + // assert + testTelemetryCollection["abc"].Should().BeNull(); + } + + [TestMethod] + public void AddTelemetryValueAlreadyExistsThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.Add("abc", 123); + + // act + Action testAction = () => testTelemetryCollection.Add("abc", 1); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateTelemetryValueAlreadyExistsSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.Add("abc", 123); + + // act + testTelemetryCollection.AddOrUpdate("abc", 1); + + // assert + testTelemetryCollection["abc"].Should().Be(1); + } + + [TestMethod] + public void AddNullTelemetryCollectionThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.Add(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateNullTelemetryCollectionThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.AddOrUpdate(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddTelemetryCollectionAlreadyExistsThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.AddOrUpdate("abc", 123); + var telemetryValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + Action testAction = () => testTelemetryCollection.Add(telemetryValues); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateTelemetryCollectionAlreadyExistsSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.AddOrUpdate("abc", 123); + var telemetryValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + testTelemetryCollection.AddOrUpdate(telemetryValues); + + // assert + testTelemetryCollection["qwe"].Should().Be(98); + testTelemetryCollection["abc"].Should().Be(2); + } + } +} From 3047a248cb0ac8150454df619f297cecdd44a160 Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Wed, 22 Sep 2021 03:21:32 +0300 Subject: [PATCH 68/77] Wrong reference to Device Streaming Sample (#2173) * Fix link to Device Streaming Sample * Update iothub/service/samples/readme.md Co-authored-by: David R. Williamson --- iothub/service/samples/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iothub/service/samples/readme.md b/iothub/service/samples/readme.md index 4003c89e3c..4460522296 100644 --- a/iothub/service/samples/readme.md +++ b/iothub/service/samples/readme.md @@ -17,11 +17,11 @@ Service samples were moved to [Azure-Samples/azure-iot-samples-csharp][samples-r [samples-repo]: https://github.com/Azure-Samples/azure-iot-samples-csharp [service-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service [adm-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/AutomaticDeviceManagementSample -[device-streaming-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/DeviceStreamingSample +[device-streaming-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/service/DeviceStreamingSample [edge-deployment-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/EdgeDeploymentSample [import-export-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/ImportExportDevicesSample [jobs-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/JobsSample [reg-man-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/RegistryManagerSample [service-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/ServiceClientSample [pnp-service-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/PnpServiceSamples -[digital-twin-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/DigitalTwinClientSamples \ No newline at end of file +[digital-twin-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/DigitalTwinClientSamples From 1a0fc1c13eb62cd9d38f7a052afb85b4d6e243f1 Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Thu, 23 Sep 2021 17:50:46 -0700 Subject: [PATCH 69/77] fix(dps-service): Remove client side validation of x509 CA references format (#2172) --- .../service/src/Config/X509CAReferences.cs | 13 +++---------- .../service/tests/Config/X509CAReferencesTests.cs | 14 -------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/provisioning/service/src/Config/X509CAReferences.cs b/provisioning/service/src/Config/X509CAReferences.cs index d445d7d566..efe1b12daf 100644 --- a/provisioning/service/src/Config/X509CAReferences.cs +++ b/provisioning/service/src/Config/X509CAReferences.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Devices.Provisioning.Service /// /// /// This class creates a representation of an X509 CA references. It can receive primary and secondary - /// CA references, but only the primary is mandatory. + /// CA references. /// /// Users must provide the CA reference as a String. This class will encapsulate both in a /// single . @@ -36,18 +36,11 @@ public class X509CAReferences /// The CA reference is a String with the name that you gave for your certificate. /// /// - /// the String with the primary CA reference. It cannot be null or empty. - /// the String with the secondary CA reference. It can be null or empty. - /// if the primary CA reference is null or empty. + /// the String with the primary CA reference. + /// the String with the secondary CA reference. [JsonConstructor] internal X509CAReferences(string primary, string secondary = null) { - /* SRS_X509_CAREFERENCE_21_001: [The constructor shall throw ArgumentException if the primary CA reference is null or empty.] */ - if(string.IsNullOrWhiteSpace(primary)) - { - throw new ProvisioningServiceClientException("Primary CA reference cannot be null or empty"); - } - /* SRS_X509_CAREFERENCE_21_002: [The constructor shall store the primary and secondary CA references.] */ Primary = primary; Secondary = secondary; } diff --git a/provisioning/service/tests/Config/X509CAReferencesTests.cs b/provisioning/service/tests/Config/X509CAReferencesTests.cs index 8f292ef792..750747ad83 100644 --- a/provisioning/service/tests/Config/X509CAReferencesTests.cs +++ b/provisioning/service/tests/Config/X509CAReferencesTests.cs @@ -9,20 +9,6 @@ namespace Microsoft.Azure.Devices.Provisioning.Service.Test [TestCategory("Unit")] public class X509CAReferencesTests { - /* SRS_X509_CAREFERENCE_21_001: [The constructor shall throw ArgumentException if the primary CA reference is null or empty.] */ - - [TestMethod] - public void X509CAReferencesThrowsOnInvalidPrimaryReferences() - { - // act and assert -#pragma warning disable CA1806 // Do not ignore method results - TestAssert.Throws(() => new X509CAReferences(null)); - TestAssert.Throws(() => new X509CAReferences("")); - TestAssert.Throws(() => new X509CAReferences(" ")); - TestAssert.Throws(() => new X509CAReferences(null, "valid-ca-reference")); -#pragma warning restore CA1806 // Do not ignore method results - } - /* SRS_X509_CAREFERENCE_21_002: [The constructor shall store the primary and secondary CA references.] */ [TestMethod] From 48163a8f45d6dba186b5937efcf3eab08a7360cd Mon Sep 17 00:00:00 2001 From: jamdavi <73593426+jamdavi@users.noreply.github.com> Date: Wed, 29 Sep 2021 11:14:49 -0600 Subject: [PATCH 70/77] Add Andy to the repos (#2179) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 407bbf8e1f..2b96ff29d2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # Track1 .NET Azure IoT Hub and DPS SDKs -* @drwill-ms @timtay-microsoft @abhipsaMisra @azabbasi @jamdavi +* @drwill-ms @timtay-microsoft @abhipsaMisra @azabbasi @jamdavi @andykwong-ms From 3d19c62c750bd1a0d0a1a29b13addaa03dfc859b Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Fri, 1 Oct 2021 20:57:02 +0000 Subject: [PATCH 71/77] Default Branch update: Update master branch to main. (#2183) * Update master branch to main. * Update horton-e2e.yaml --- .github/CONTRIBUTING.md | 18 +++++++------- .github/ISSUE_TEMPLATE.md | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- .github/PULL_REQUEST_TEMPLATE.md | 6 ++--- device_connection_and_reliability_readme.md | 10 ++++---- e2e/test/prerequisites/readme.md | 6 ++--- iothub/device/devdoc/architecture.md | 2 +- iothub/device/samples/readme.md | 24 +++++++++---------- .../IotHubCommunicationException.cs | 2 +- iothub/service/samples/readme.md | 18 +++++++------- iothub/service/src/DigitalTwin/readme.md | 2 +- provisioning/device/samples/readme.md | 6 ++--- .../device/src/CertificateInstaller.cs | 2 +- provisioning/service/samples/readme.md | 10 ++++---- readme.md | 6 ++--- tools/CaptureLogs/readme.md | 4 ++-- tools/diffscripts/README.md | 20 ++++++++-------- tools/diffscripts/diffapi.ps1 | 22 ++++++++--------- vsts/vsts.yaml | 2 +- 19 files changed, 83 insertions(+), 83 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c7f7c515ae..e6f4ddf2d8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,13 +27,13 @@ That is definitely something we want to hear about. Please open an issue on gith ## Contribute documentation -For simple markdown files, we accept documentation pull requests submitted against the `master` branch, if it's about existing SDK features. +For simple markdown files, we accept documentation pull requests submitted against the `main` branch, if it's about existing SDK features. If your PR is about future changes or has changes to the comments in the code itself, we'll treat is as a code change (see the next section). ## Contribute code Our SDKs are open-source and we do accept pull-requests if you feel like taking a stab at fixing the bug and maybe adding your name to our commit history :) Please mention any relevant issue number in the pull request description, and follow the contributing guidelines [below](#contributing-guidelines). -Pull-requests for code are to be submitted against the `master` branch. We will review the request and once approved we will be running it in our gated build system. We try to maintain a high bar for code quality and maintainability, we insist on having tests associated with the code, and if necessary, additions/modifications to the requirement documents. +Pull-requests for code are to be submitted against the `main` branch. We will review the request and once approved we will be running it in our gated build system. We try to maintain a high bar for code quality and maintainability, we insist on having tests associated with the code, and if necessary, additions/modifications to the requirement documents. Also, have you signed the [Contribution License Agreement](https://cla.microsoft.com/) ([CLA](https://cla.microsoft.com/))? A friendly bot will remind you about it when you submit your pull-request. @@ -44,9 +44,9 @@ sure your plans and ours are in sync :) Just open an issue on github and tag it 1. If the change affects the public API, extract the updated public API surface and submit a PR for review. Make sure you get a signoff before you move to Step 2. 2. Post API surface approval, follow the below guidelines for contributing code: - a) Follow the steps [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/doc/devbox_setup.md) for setting up your development environment. + a) Follow the steps [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/doc/devbox_setup.md) for setting up your development environment. - b) Follow the [C# Coding Style](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/doc/coding-style.md). + b) Follow the [C# Coding Style](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/doc/coding-style.md). c) Unit Tests: We write unit tests for any new function or block of application code that impacts the existing behavior of the code. @@ -73,9 +73,9 @@ sure your plans and ours are in sync :) Just open an issue on github and tag it ``` d) E2E Tests: Any new feature or functionality added must have associated end-to-end tests. - 1. Update/ Add the E2E tests [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/e2e/test). - 2. In case environmental setup required for the application is changed, update the pre-requisites [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/e2e/test/prerequisites). - 3. Run the E2E test suite and ensure that all the tests pass successfully. You can also test against the [CI script](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/jenkins/windows_csharp.cmd) that is used in our gated build system. + 1. Update/ Add the E2E tests [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/main/e2e/test). + 2. In case environmental setup required for the application is changed, update the pre-requisites [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/main/e2e/test/prerequisites). + 3. Run the E2E test suite and ensure that all the tests pass successfully. You can also test against the [CI script](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/jenkins/windows_csharp.cmd) that is used in our gated build system. e) Samples: Add relevant samples to the [Azure IoT Samples for C# Repo](https://github.com/Azure-Samples/azure-iot-samples-csharp). Make sure to add a supporting readme file demonstrating the steps to run the sample. @@ -100,7 +100,7 @@ sure your plans and ours are in sync :) Just open an issue on github and tag it } } ``` -3. Post completion of all of the above steps, create a PR against `master`. +3. Post completion of all of the above steps, create a PR against `main`. #### Commit Guidelines We have very precise rules over how our git commit messages can be formatted. This leads to more readable messages that are easy to follow when looking through the project history. @@ -130,7 +130,7 @@ If the commit reverts a previous commit, it should begin with `revert:`, followe **Rebase and Squash** -* Its manadatory to squash all your commits per scope (i.e package). It is also important to rebase your commits on master. +* Its manadatory to squash all your commits per scope (i.e package). It is also important to rebase your commits on `main`. * Optionally you can split your commits on the basis of the package you are providing code to. **Type** diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 767cfeaab6..f597f4b4aa 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -24,5 +24,5 @@ Please use your Azure subscription if you need to share any information from you ## Console log of the issue: - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fda342332a..cbc7cce44d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,7 +25,7 @@ Please follow the instructions and template below to save us time requesting add 4. Include enough information for us to address the bug: - A detailed description. - A [Minimal Complete Reproducible Example](https://stackoverflow.com/help/mcve). This is code we can cut and paste into a readily available sample and run, or a link to a project you've written that we can compile to reproduce the bug. - - Console logs (https://github.com/Azure/azure-iot-sdk-csharp/tree/master/tools/CaptureLogs). + - Console logs (https://github.com/Azure/azure-iot-sdk-csharp/tree/main/tools/CaptureLogs). 5. Delete these instructions before submitting the bug. @@ -51,5 +51,5 @@ Please be as detailed as possible: which feature has a problem, how often does i Please remove any connection string information! ## Console log of the issue -Follow the instructions [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/tools/CaptureLogs) to capture SDK logs. +Follow the instructions [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/main/tools/CaptureLogs) to capture SDK logs. Don't forget to remove any connection string information! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 857a0a6add..2dfffb1f67 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,10 +9,10 @@ Need support? --> ## Checklist -- [ ] I have read the [contribution guidelines](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/.github/CONTRIBUTING.md). +- [ ] I have read the [contribution guidelines](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/.github/CONTRIBUTING.md). - [ ] I added or modified the existing tests to cover the change (we do not allow our test coverage to go down). -- [ ] This pull-request is submitted against the `master` branch. - +- [ ] This pull-request is submitted against the `main` branch. + ## Description of the changes diff --git a/device_connection_and_reliability_readme.md b/device_connection_and_reliability_readme.md index dcdd357930..ff4694bc6a 100644 --- a/device_connection_and_reliability_readme.md +++ b/device_connection_and_reliability_readme.md @@ -19,10 +19,10 @@ Authentication can be done using one of the following: - [TPM based authentication](https://azure.microsoft.com/blog/device-provisioning-identity-attestation-with-tpm/) Samples: -- IoT hub device shared access key based authentication sample - [DeviceReconnectionSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/iot-hub/Samples/device/DeviceReconnectionSample/DeviceReconnectionSample.cs#L102) -- Device provisioning service symmetric key based authentication sample - [ProvisioningDeviceClientSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/provisioning/Samples/device/SymmetricKeySample/ProvisioningDeviceClientSample.cs#L62) -- x509 based authentication sample using CA-signed certificates - [X509DeviceCertWithChainSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/iot-hub/Samples/device/X509DeviceCertWithChainSample/Program.cs#L43) -- TPM based authentication sample - [ProvisioningDeviceClientSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/provisioning/Samples/device/TpmSample/ProvisioningDeviceClientSample.cs#L49) +- IoT hub device shared access key based authentication sample - [DeviceReconnectionSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/main/iot-hub/Samples/device/DeviceReconnectionSample/DeviceReconnectionSample.cs#L102) +- Device provisioning service symmetric key based authentication sample - [ProvisioningDeviceClientSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/main/provisioning/Samples/device/SymmetricKeySample/ProvisioningDeviceClientSample.cs#L62) +- x509 based authentication sample using CA-signed certificates - [X509DeviceCertWithChainSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/main/iot-hub/Samples/device/X509DeviceCertWithChainSample/Program.cs#L43) +- TPM based authentication sample - [ProvisioningDeviceClientSample](https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/main/provisioning/Samples/device/TpmSample/ProvisioningDeviceClientSample.cs#L49) When using SAS tokens, authentication can be done by: @@ -64,7 +64,7 @@ For both AMQP and MQTT, the SDK will try to reconnect anytime there is any netwo > Note: The default retry policy has support for jitter, which ensures that if you have N devices that disconnected at the same time, all of them won't start reconnecting with the same delay. -For more details on the default retry policy and how to override it, see [retry policy documentation](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/devdoc/retrypolicy.md). +For more details on the default retry policy and how to override it, see [retry policy documentation](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/iothub/device/devdoc/retrypolicy.md). HTTP is a stateless protocol and will work whenever there is network connectivity. diff --git a/e2e/test/prerequisites/readme.md b/e2e/test/prerequisites/readme.md index edfa8ad4fe..680d898a5e 100644 --- a/e2e/test/prerequisites/readme.md +++ b/e2e/test/prerequisites/readme.md @@ -1,11 +1,11 @@ # Azure IoT C# End-to-end test prerequisites -The E2E tests require some Azure resources to be set up and configured. Running the [e2eTestsSetup.ps1](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1) powershell script is a convenient way of getting all the resources setup with the required configuration. +The E2E tests require some Azure resources to be set up and configured. Running the [e2eTestsSetup.ps1](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1) powershell script is a convenient way of getting all the resources setup with the required configuration. -Note: The [e2eTestsSetup.ps1](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1) script will setup all the resources necessary to run the full test suite. Ensure to delete these resources when not required as they will cost money. If you want to specifically create some resources, you can take a look at the script for help. +Note: The [e2eTestsSetup.ps1](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1) script will setup all the resources necessary to run the full test suite. Ensure to delete these resources when not required as they will cost money. If you want to specifically create some resources, you can take a look at the script for help. -- Navigate to [e2eTestsSetup.ps1](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1) +- Navigate to [e2eTestsSetup.ps1](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/e2e/test/prerequisites/E2ETestsSetup/e2eTestsSetup.ps1) - Open powershell in Administrator mode and run the following command by replacing the variables in brackets with your own preferred values. diff --git a/iothub/device/devdoc/architecture.md b/iothub/device/devdoc/architecture.md index 4c903187e9..07d988d7e0 100644 --- a/iothub/device/devdoc/architecture.md +++ b/iothub/device/devdoc/architecture.md @@ -138,7 +138,7 @@ Immediate cancellation is achieved by using `Dispose()` which closes and dispose | `(ConnectionStatus.Disabled, ConnectionStatusChangeReason.Client_Close)` | Application disposed the client. | | `(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.Communication_Error)` | If no callback subscriptions exist, the client will not automatically connect. A future operation will attempt to reconnect the client. | | `(ConnectionStatus.Disconnected_Retrying, ConnectionStatusChangeReason.Communication_Error)` | If any callback subscriptions exist (methods, twin, events) and connectivity is lost, the client will try to reconnect. | -| `(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.Retry_Expired)` | Retry timeout. The `RetryDelegatingHandler` will attempt to recover links for a duration of `OperationTimeoutInMilliseconds` (default 4 minutes) according to [this retry policy](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/devdoc/requirements/retrypolicy.md). | +| `(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.Retry_Expired)` | Retry timeout. The `RetryDelegatingHandler` will attempt to recover links for a duration of `OperationTimeoutInMilliseconds` (default 4 minutes) according to [this retry policy](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/iothub/device/devdoc/requirements/retrypolicy.md). | | `(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.Bad_Credential)` | UnauthorizedException during Retry. | | `(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.Device_Disabled)` | DeviceDisabledException during Retry. | diff --git a/iothub/device/samples/readme.md b/iothub/device/samples/readme.md index 3908583189..d2e8c60316 100644 --- a/iothub/device/samples/readme.md +++ b/iothub/device/samples/readme.md @@ -65,18 +65,18 @@ You need to clone the repository or download the sample (the one you want to try dotnet run ``` -[device-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device -[d-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/DeviceReconnectionSample -[d-receive-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/MessageReceiveSample -[d-method-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/MethodSample -[d-twin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/TwinSample -[d-file-upload-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/FileUploadSample -[d-x509-cert-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/X509DeviceCertWithChainSample -[d-import-export-devices-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/ImportExportDevicesSample -[d-pnp-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/PnpDeviceSamples -[d-xamarin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/XamarinSample - -[m-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/module/ModuleSample +[device-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device +[d-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/DeviceReconnectionSample +[d-receive-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/MessageReceiveSample +[d-method-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/MethodSample +[d-twin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/TwinSample +[d-file-upload-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/FileUploadSample +[d-x509-cert-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/X509DeviceCertWithChainSample +[d-import-export-devices-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/ImportExportDevicesSample +[d-pnp-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/PnpDeviceSamples +[d-xamarin-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/XamarinSample + +[m-message-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/module/ModuleSample [lnk-setup-iot-hub]: https://aka.ms/howtocreateazureiothub [lnk-manage-iot-device]: https://github.com/Azure/azure-iot-device-ecosystem/blob/master/setup_iothub.md#create-new-device-in-the-iot-hub-device-identity-registry \ No newline at end of file diff --git a/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs b/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs index 165433891e..5332b29ce2 100644 --- a/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs +++ b/iothub/device/src/Common/Exceptions/IotHubCommunicationException.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.Devices.Client.Exceptions /// /// /// By default, the SDK indefinitely retries dropped connections, unless the retry policy is overridden. - /// For more information on the SDK's retry policy and how to override it, see . + /// For more information on the SDK's retry policy and how to override it, see . /// When the exception is thrown due to operation timeouts, the inner exception will have OperationCanceledException. /// Retrying operations failed due to timeouts could resolve the error. /// diff --git a/iothub/service/samples/readme.md b/iothub/service/samples/readme.md index 4460522296..196360f4a1 100644 --- a/iothub/service/samples/readme.md +++ b/iothub/service/samples/readme.md @@ -15,13 +15,13 @@ Service samples were moved to [Azure-Samples/azure-iot-samples-csharp][samples-r [samples-repo]: https://github.com/Azure-Samples/azure-iot-samples-csharp -[service-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service -[adm-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/AutomaticDeviceManagementSample +[service-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service +[adm-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/AutomaticDeviceManagementSample [device-streaming-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/preview/iot-hub/Samples/service/DeviceStreamingSample -[edge-deployment-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/EdgeDeploymentSample -[import-export-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/ImportExportDevicesSample -[jobs-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/JobsSample -[reg-man-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/RegistryManagerSample -[service-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/ServiceClientSample -[pnp-service-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/PnpServiceSamples -[digital-twin-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/DigitalTwinClientSamples +[edge-deployment-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/EdgeDeploymentSample +[import-export-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/ImportExportDevicesSample +[jobs-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/JobsSample +[reg-man-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/RegistryManagerSample +[service-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/ServiceClientSample +[pnp-service-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/PnpServiceSamples +[digital-twin-client-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/DigitalTwinClientSamples diff --git a/iothub/service/src/DigitalTwin/readme.md b/iothub/service/src/DigitalTwin/readme.md index 1c0f96fb6a..a937bc9f05 100644 --- a/iothub/service/src/DigitalTwin/readme.md +++ b/iothub/service/src/DigitalTwin/readme.md @@ -6,7 +6,7 @@ ### Examples -You can familiarize yourself with different APIs using [samples for DigitalTwinClient](https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/service/DigitalTwinClientSamples). +You can familiarize yourself with different APIs using [samples for DigitalTwinClient](https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/service/DigitalTwinClientSamples). ## Source code folder structure diff --git a/provisioning/device/samples/readme.md b/provisioning/device/samples/readme.md index c59f6f4629..e80c59f380 100644 --- a/provisioning/device/samples/readme.md +++ b/provisioning/device/samples/readme.md @@ -6,6 +6,6 @@ Device provisioning samples were moved to [Azure-Samples/azure-iot-samples-cshar [samples-repo]: https://github.com/Azure-Samples/azure-iot-samples-csharp -[service-device-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/device -[x509-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/device/X509Sample -[tpm-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/device/TpmSample \ No newline at end of file +[service-device-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/device +[x509-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/device/X509Sample +[tpm-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/device/TpmSample \ No newline at end of file diff --git a/provisioning/device/src/CertificateInstaller.cs b/provisioning/device/src/CertificateInstaller.cs index 077ed41770..3bcf93a907 100644 --- a/provisioning/device/src/CertificateInstaller.cs +++ b/provisioning/device/src/CertificateInstaller.cs @@ -17,7 +17,7 @@ internal static class CertificateInstaller /// /// Because Intermediate Authorities may have been issued by the uploaded CA, the application must present the full chain of /// certificates from the one used during authentication to the one uploaded to the service. - /// See + /// See /// for more information. /// /// The certificate chain to ensure is installed. diff --git a/provisioning/service/samples/readme.md b/provisioning/service/samples/readme.md index 66d67982d9..6dce11524d 100644 --- a/provisioning/service/samples/readme.md +++ b/provisioning/service/samples/readme.md @@ -7,8 +7,8 @@ Service provisioning samples were moved to [Azure-Samples/azure-iot-samples-csha * [EnrollmentGroupSample][enrollment-group-sample] [samples-repo]: https://github.com/Azure-Samples/azure-iot-samples-csharp -[service-prov-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/service -[group-cert-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/service/GroupCertificateVerificationSample -[bulk-op-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/service/BulkOperationSample -[enrollment-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/service/EnrollmentSample -[enrollment-group-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/provisioning/Samples/service/EnrollmentGroupSample \ No newline at end of file +[service-prov-samples]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/service +[group-cert-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/service/GroupCertificateVerificationSample +[bulk-op-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/service/BulkOperationSample +[enrollment-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/service/EnrollmentSample +[enrollment-group-sample]: https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/provisioning/Samples/service/EnrollmentGroupSample \ No newline at end of file diff --git a/readme.md b/readme.md index aa4ef97d86..820897f0f0 100644 --- a/readme.md +++ b/readme.md @@ -31,7 +31,7 @@ Due to security considerations, build logs are not publicly available. | Service Environment | Status | | --- | --- | -| [Master](https://github.com/Azure/azure-iot-sdk-csharp/tree/master) | [![Build Status](https://azure-iot-sdks.visualstudio.com/azure-iot-sdks/_apis/build/status/csharp/CSharp%20Prod%20-%20West%20Central%20US?branchName=master)](https://azure-iot-sdks.visualstudio.com/azure-iot-sdks/_build/latest?definitionId=44&repositoryFilter=9&branchName=master) | +| [Main](https://github.com/Azure/azure-iot-sdk-csharp/tree/main) | [![Build Status](https://azure-iot-sdks.visualstudio.com/azure-iot-sdks/_apis/build/status/csharp/CSharp%20Prod%20-%20West%20Central%20US?branchName=main)](https://azure-iot-sdks.visualstudio.com/azure-iot-sdks/_build/latest?definitionId=44&repositoryFilter=9&branchName=main) | | [Preview](https://github.com/Azure/azure-iot-sdk-csharp/tree/preview) | [![Build Status](https://azure-iot-sdks.visualstudio.com/azure-iot-sdks/_apis/build/status/csharp/CSharp%20Canary%20-%20Central%20US%20EUAP?branchName=preview)](https://azure-iot-sdks.visualstudio.com/azure-iot-sdks/_build/latest?definitionId=402&repositoryFilter=9&branchName=preview) | ### Recommended NuGet packages @@ -81,7 +81,7 @@ Visit [Azure IoT Dev Center][iot-dev-center] to learn more about developing appl Most of our samples are available at [Azure IoT Samples for C#](https://github.com/Azure-Samples/azure-iot-samples-csharp). -If you are looking for a good device sample to get started with, please see the [device reconnection sample](https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/master/iot-hub/Samples/device/DeviceReconnectionSample). +If you are looking for a good device sample to get started with, please see the [device reconnection sample](https://github.com/Azure-Samples/azure-iot-samples-csharp/tree/main/iot-hub/Samples/device/DeviceReconnectionSample). It shows how to connect a device, handle disconnect events, cases to handle when making calls, and when to re-initialize the `DeviceClient`. ## Contribute to the Azure IoT C# SDK @@ -165,7 +165,7 @@ This repository contains [provisioning service client SDK](./provisioning/servic - [Set up your development environment](./doc/devbox_setup.md) to prepare your development environment as well as how to run the samples on Linux, Windows or other platforms. - [API reference documentation for .NET](https://docs.microsoft.com/dotnet/api/overview/azure/devices?view=azure-dotnet) - [Get Started with IoT Hub using .NET](https://docs.microsoft.com/azure/iot-hub/iot-hub-csharp-csharp-getstarted) -- [Device connection and messaging reliability](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/device_connection_and_reliability_readme.md) +- [Device connection and messaging reliability](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/device_connection_and_reliability_readme.md) > Device Explorer is no longer supported. A replacement tool can be found [here](https://github.com/Azure/azure-iot-explorer). diff --git a/tools/CaptureLogs/readme.md b/tools/CaptureLogs/readme.md index 6d0efa77ae..2918d1f1dd 100644 --- a/tools/CaptureLogs/readme.md +++ b/tools/CaptureLogs/readme.md @@ -11,7 +11,7 @@ We have provided the following convenience scripts for log collection using `log 1. `-TraceName` - the name of the event trace data collector. This can be any name that will be used to identity the collector created. 2. `-Output` - the output log file that will be created. This should be a `.etl` file. 3. `-ProviderFile` - The file listing multiple Event Trace providers to enable. The file should be a text file containing one provider per line. - The Azure IoT SDK providers file is present [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/tools/CaptureLogs/iot_providers.txt). The providers list with their corresponding package details are present [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/master/tools/CaptureLogs#azure-iot-sdk-providers). + The Azure IoT SDK providers file is present [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/tools/CaptureLogs/iot_providers.txt). The providers list with their corresponding package details are present [here](https://github.com/Azure/azure-iot-sdk-csharp/tree/main/tools/CaptureLogs#azure-iot-sdk-providers). Sample usage: @@ -35,7 +35,7 @@ On Linux and OSX LTTNG and perfcollect can be used to collect traces. For more i ## Console logging Logging can be added to console. Note that this method will substantially slow down execution. - 1. Add [`e2e\test\helpers\ConsoleEventListener.cs`](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/e2e/test/helpers/ConsoleEventListener.cs) to your project. + 1. Add [`e2e\test\helpers\ConsoleEventListener.cs`](https://github.com/Azure/azure-iot-sdk-csharp/blob/main/e2e/test/helpers/ConsoleEventListener.cs) to your project. 2. Instantiate the listener. Add one or more filters (e.g. `Microsoft-Azure-` or `DotNetty-`): ```csharp diff --git a/tools/diffscripts/README.md b/tools/diffscripts/README.md index e222c78f17..d79ef25b18 100644 --- a/tools/diffscripts/README.md +++ b/tools/diffscripts/README.md @@ -108,7 +108,7 @@ VERBOSE: Repository base path: C:\repos VERBOSE: AsmDiff executable: dotnet-asmdiff.exe VERBOSE: Using user supplied iot-sdk-internals repository. VERBOSE: Using C:\adtexplorer\ for the internals sdk repository base directory. -VERBOSE: Directory where the SDK markdown files will be generated: C:\adtexplorer\sdk_design_docs\CSharp\master +VERBOSE: Directory where the SDK markdown files will be generated: C:\adtexplorer\sdk_design_docs\CSharp\main ... ... ``` @@ -193,15 +193,15 @@ Creating markdown for C:\repos\azure-iot-sdk-csharp\provisioning\transport\http\ Creating markdown for C:\repos\azure-iot-sdk-csharp\security\tpm\src\bin\Release\netstandard2.1\Microsoft.Azure.Devices.Provisioning.Security.Tpm.dll Changes have been detected. Verify each file listed below to be sure of the scope of changes. -There have been 3 deletions and 9 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Client.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Client.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Security.Tpm.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Service.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Transport.Http.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.md -There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Shared.md -There have been 9 deletions and 7 additions to sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.md +There have been 3 deletions and 9 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Client.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Client.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Security.Tpm.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Service.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Transport.Http.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.md +There have been 0 deletions and 2 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Shared.md +There have been 9 deletions and 7 additions to sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.md Finished generating the markdown files for comparison. Review the output above for release notes and to determine if there are version changes. ``` \ No newline at end of file diff --git a/tools/diffscripts/diffapi.ps1 b/tools/diffscripts/diffapi.ps1 index d6ca222165..4fc14c4d0c 100644 --- a/tools/diffscripts/diffapi.ps1 +++ b/tools/diffscripts/diffapi.ps1 @@ -64,7 +64,7 @@ Param( })] # The path of the iot-sdk-internals repository (ex: c:\repo\iot-sdks-internals) [System.IO.FileInfo] $SDKInternalsPath = $null, - # Indicates you will compare the output to the last preview version instead of master + # Indicates you will compare the output to the last preview version instead of main [switch] $IsPreview ) @@ -137,7 +137,7 @@ else Write-Verbose "Using $internalRootPath for the internals sdk repository base directory." # If we specify to use the preview directory on the command line we will set it as such -$compareDirectory = Join-Path -Path $internalRootPath -Child "\sdk_design_docs\CSharp\master" +$compareDirectory = Join-Path -Path $internalRootPath -Child "\sdk_design_docs\CSharp\main" if ($IsPreview) { $compareDirectory = Join-Path -Path $internalRootPath -Child "\sdk_design_docs\CSharp\preview" @@ -302,15 +302,15 @@ Set-Location -Path $compareDirectory # # # https://git-scm.com/docs/git-diff # -# 9 3 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Client.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Client.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Security.Tpm.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Service.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Transport.Http.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.md -# 2 0 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.Shared.md -# 7 9 sdk_design_docs/CSharp/master/Microsoft.Azure.Devices.md +# 9 3 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Client.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Client.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Security.Tpm.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Service.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Transport.Amqp.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Transport.Http.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Provisioning.Transport.Mqtt.md +# 2 0 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.Shared.md +# 7 9 sdk_design_docs/CSharp/main/Microsoft.Azure.Devices.md $gitDiffOutput = git diff --ignore-all-space --numstat Write-Verbose "Output off git diff --ignore-all-space --numstat" diff --git a/vsts/vsts.yaml b/vsts/vsts.yaml index 6d2c65c4c8..a7f8c7de81 100644 --- a/vsts/vsts.yaml +++ b/vsts/vsts.yaml @@ -4,7 +4,7 @@ trigger: batch: true branches: include: - - master + - main paths: exclude: - docs/* From 57629ffa26a1e1d5c4a4dbcf770d0527888ce5ab Mon Sep 17 00:00:00 2001 From: timtay-microsoft Date: Mon, 4 Oct 2021 13:13:12 -0700 Subject: [PATCH 72/77] fix(iot-serv): Fix bug where device scope and parent scopes set to device weren't used in bulk add operations (#2189) #2184 --- .../iothub/service/RegistryManagerE2ETests.cs | 11 +++++++++-- iothub/service/src/ExportImportDevice.cs | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/e2e/test/iothub/service/RegistryManagerE2ETests.cs b/e2e/test/iothub/service/RegistryManagerE2ETests.cs index 0e4c681fcb..c8a1550281 100644 --- a/e2e/test/iothub/service/RegistryManagerE2ETests.cs +++ b/e2e/test/iothub/service/RegistryManagerE2ETests.cs @@ -121,7 +121,10 @@ public async Task RegistryManager_BulkLifecycle() var devices = new List(); for (int i = 0; i < bulkCount; i++) { - devices.Add(new Device(_devicePrefix + Guid.NewGuid())); + var device = new Device(_devicePrefix + Guid.NewGuid()); + device.Scope = "someScope" + Guid.NewGuid(); + device.ParentScopes.Add("someParentScope" + Guid.NewGuid()); + devices.Add(device); } using RegistryManager registryManager = RegistryManager.CreateFromConnectionString(TestConfiguration.IoTHub.ConnectionString); @@ -133,7 +136,11 @@ public async Task RegistryManager_BulkLifecycle() foreach (Device device in devices) { // After a bulk add, every device should be able to be retrieved - Assert.IsNotNull(await registryManager.GetDeviceAsync(device.Id).ConfigureAwait(false)); + Device retrievedDevice = await registryManager.GetDeviceAsync(device.Id).ConfigureAwait(false); + Assert.IsNotNull(retrievedDevice.Id); + Assert.AreEqual(device.Scope, retrievedDevice.Scope); + Assert.AreEqual(1, retrievedDevice.ParentScopes.Count); + Assert.AreEqual(device.ParentScopes.ElementAt(0), retrievedDevice.ParentScopes.ElementAt(0)); } var twins = new List(); diff --git a/iothub/service/src/ExportImportDevice.cs b/iothub/service/src/ExportImportDevice.cs index eacfc12b6d..79d1a60a9e 100644 --- a/iothub/service/src/ExportImportDevice.cs +++ b/iothub/service/src/ExportImportDevice.cs @@ -5,6 +5,7 @@ // --------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; @@ -64,6 +65,8 @@ public ExportImportDevice(Device device, ImportMode importmode) StatusReason = device.StatusReason; Authentication = device.Authentication; Capabilities = device.Capabilities; + DeviceScope = device.Scope; + ParentScopes = device.ParentScopes; } /// @@ -152,6 +155,21 @@ public string TwinETag [JsonProperty(PropertyName = "deviceScope", NullValueHandling = NullValueHandling.Include)] public string DeviceScope { get; set; } + /// + /// The scopes of the upper level edge devices if applicable. + /// + /// + /// For edge devices, the value to set a parent edge device can be retrieved from the parent edge device's property. + /// + /// For leaf devices, this could be set to the same value as or left for the service to copy over. + /// + /// For now, this list can only have 1 element in the collection. + /// + /// For more information, see . + /// + [JsonProperty(PropertyName = "parentScopes", NullValueHandling = NullValueHandling.Ignore)] + public IList ParentScopes { get; internal set; } = new List(); + private static string SanitizeETag(string eTag) { if (!string.IsNullOrWhiteSpace(eTag)) From caf785072330a61b33518ce49a7efcad080f60c9 Mon Sep 17 00:00:00 2001 From: "David R. Williamson" Date: Mon, 4 Oct 2021 16:25:49 -0700 Subject: [PATCH 73/77] fix(iot-svc): Cleanup and deprecation warning of code in CryptoKeyGenerator (#2187) * fix(iot-svc): Add support for GeneratePassword to other targets * Don't add net472 support, but keep code cleanup * Add deprecated attribute * Fix test references by making a local copy for tests --- e2e/test/helpers/CryptoKeyGenerator.cs | 62 +++++++++++++ .../ProvisioningServiceClientE2ETests.cs | 2 - .../src/Common/Security/CryptoKeyGenerator.cs | 86 +++++++++++-------- iothub/service/tests/CryptoKeyGenerator.cs | 62 +++++++++++++ .../tests/DeviceAuthenticationTests.cs | 2 +- 5 files changed, 174 insertions(+), 40 deletions(-) create mode 100644 e2e/test/helpers/CryptoKeyGenerator.cs create mode 100644 iothub/service/tests/CryptoKeyGenerator.cs diff --git a/e2e/test/helpers/CryptoKeyGenerator.cs b/e2e/test/helpers/CryptoKeyGenerator.cs new file mode 100644 index 0000000000..8c4f9c82e2 --- /dev/null +++ b/e2e/test/helpers/CryptoKeyGenerator.cs @@ -0,0 +1,62 @@ +// 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.Security.Cryptography; + +#if !NET451 + +using System.Linq; + +#endif + +namespace Microsoft.Azure.Devices.E2ETests +{ + /// + /// Utility methods for generating cryptographically secure keys and passwords. + /// + internal static class CryptoKeyGenerator + { +#if NET451 + private const int DefaultPasswordLength = 16; + private const int GuidLength = 16; +#endif + + /// + /// Size of the SHA 512 key. + /// + internal const int Sha512KeySize = 64; + + /// + /// Generate a key with a specified key size. + /// + /// The size of the key. + /// Byte array representing the key. + internal static byte[] GenerateKeyBytes(int keySize) + { +#if NET451 + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = new RNGCryptoServiceProvider(); + cyptoProvider.GetNonZeroBytes(keyBytes); +#else + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = RandomNumberGenerator.Create(); + while (keyBytes.Contains(byte.MinValue)) + { + cyptoProvider.GetBytes(keyBytes); + } +#endif + return keyBytes; + } + + /// + /// Generates a key of the specified size. + /// + /// Desired key size. + /// A generated key. + internal static string GenerateKey(int keySize) + { + return Convert.ToBase64String(GenerateKeyBytes(keySize)); + } + } +} diff --git a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs index 72ee57237b..3757ae6825 100644 --- a/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs +++ b/e2e/test/provisioning/ProvisioningServiceClientE2ETests.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Tracing; using System.Net; using System.Threading.Tasks; -using Microsoft.Azure.Devices.Common; using Microsoft.Azure.Devices.Provisioning.Security.Samples; using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; diff --git a/iothub/service/src/Common/Security/CryptoKeyGenerator.cs b/iothub/service/src/Common/Security/CryptoKeyGenerator.cs index a5323235e2..03b082ba10 100644 --- a/iothub/service/src/Common/Security/CryptoKeyGenerator.cs +++ b/iothub/service/src/Common/Security/CryptoKeyGenerator.cs @@ -1,17 +1,20 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Linq; using System; using System.Text; +using System.Security.Cryptography; + +#if NET451 + +using System.Web.Security; + +#endif #if !NET451 -using System.Security.Cryptography; +using System.Linq; -#else - using System.Web.Security; - using System.Security.Cryptography; #endif namespace Microsoft.Azure.Devices.Common @@ -19,11 +22,11 @@ namespace Microsoft.Azure.Devices.Common /// /// Utility methods for generating cryptographically secure keys and passwords. /// - static public class CryptoKeyGenerator + public static class CryptoKeyGenerator { #if NET451 - const int DefaultPasswordLength = 16; - const int GuidLength = 16; + private const int DefaultPasswordLength = 16; + private const int GuidLength = 16; #endif /// @@ -36,22 +39,19 @@ static public class CryptoKeyGenerator /// /// The size of the key. /// Byte array representing the key. + [Obsolete("This method will be deprecated in a future version.")] public static byte[] GenerateKeyBytes(int keySize) { -#if !NET451 - var keyBytes = new byte[keySize]; - using (var cyptoProvider = RandomNumberGenerator.Create()) - { - while (keyBytes.Contains(byte.MinValue)) - { - cyptoProvider.GetBytes(keyBytes); - } - } +#if NET451 + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = new RNGCryptoServiceProvider(); + cyptoProvider.GetNonZeroBytes(keyBytes); #else - var keyBytes = new byte[keySize]; - using (var cyptoProvider = new RNGCryptoServiceProvider()) + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = RandomNumberGenerator.Create(); + while (keyBytes.Contains(byte.MinValue)) { - cyptoProvider.GetNonZeroBytes(keyBytes); + cyptoProvider.GetBytes(keyBytes); } #endif return keyBytes; @@ -62,6 +62,7 @@ public static byte[] GenerateKeyBytes(int keySize) /// /// Desired key size. /// A generated key. + [Obsolete("This method will be deprecated in a future version.")] public static string GenerateKey(int keySize) { return Convert.ToBase64String(GenerateKeyBytes(keySize)); @@ -72,14 +73,14 @@ public static string GenerateKey(int keySize) /// Generate a hexadecimal key of the specified size. /// /// Desired key size. - /// A generated hexadecimal key. + /// A generated hexadecimal key. + [Obsolete("This method will not be carried forward to newer .NET targets.")] public static string GenerateKeyInHex(int keySize) { - var keyBytes = new byte[keySize]; - using (var cyptoProvider = new RNGCryptoServiceProvider()) - { - cyptoProvider.GetNonZeroBytes(keyBytes); - } + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = new RNGCryptoServiceProvider(); + cyptoProvider.GetNonZeroBytes(keyBytes); + return BitConverter.ToString(keyBytes).Replace("-", ""); } @@ -87,29 +88,39 @@ public static string GenerateKeyInHex(int keySize) /// Generate a GUID using random bytes from the framework's cryptograpically strong RNG (Random Number Generator). /// /// A cryptographically secure GUID. + [Obsolete("This method will not be carried forward to newer .NET targets.")] public static Guid GenerateGuid() { byte[] bytes = new byte[GuidLength]; - using (var rng = new RNGCryptoServiceProvider()) - { - rng.GetBytes(bytes); - } + using var rng = new RNGCryptoServiceProvider(); + rng.GetBytes(bytes); - var time = BitConverter.ToUInt32(bytes, 0); - var time_mid = BitConverter.ToUInt16(bytes, 4); - var time_hi_and_ver = BitConverter.ToUInt16(bytes, 6); - time_hi_and_ver = (ushort)((time_hi_and_ver | 0x4000) & 0x4FFF); + uint time = BitConverter.ToUInt32(bytes, 0); + ushort timeMid = BitConverter.ToUInt16(bytes, 4); + ushort timeHiAndVer = BitConverter.ToUInt16(bytes, 6); + timeHiAndVer = (ushort)((timeHiAndVer | 0x4000) & 0x4FFF); bytes[8] = (byte)((bytes[8] | 0x80) & 0xBF); - return new Guid(time, time_mid, time_hi_and_ver, bytes[8], bytes[9], - bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]); + return new Guid( + time, + timeMid, + timeHiAndVer, + bytes[8], + bytes[9], + bytes[10], + bytes[11], + bytes[12], + bytes[13], + bytes[14], + bytes[15]); } /// /// Generate a unique password with a default length and without converting it to Base64String. /// /// A unique password. + [Obsolete("This method will not be carried forward to newer .NET targets.")] public static string GeneratePassword() { return GeneratePassword(DefaultPasswordLength, false); @@ -121,9 +132,10 @@ public static string GeneratePassword() /// Desired length of the password. /// Encode the password if set to True. False otherwise. /// A generated password. + [Obsolete("This method will not be carried forward to newer .NET targets.")] public static string GeneratePassword(int length, bool base64Encoding) { - var password = Membership.GeneratePassword(length, length / 2); + string password = Membership.GeneratePassword(length, length / 2); if (base64Encoding) { password = Convert.ToBase64String(Encoding.UTF8.GetBytes(password)); diff --git a/iothub/service/tests/CryptoKeyGenerator.cs b/iothub/service/tests/CryptoKeyGenerator.cs new file mode 100644 index 0000000000..30cd86c15d --- /dev/null +++ b/iothub/service/tests/CryptoKeyGenerator.cs @@ -0,0 +1,62 @@ +// 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.Security.Cryptography; + +#if !NET451 + +using System.Linq; + +#endif + +namespace Microsoft.Azure.Devices.Tests +{ + /// + /// Utility methods for generating cryptographically secure keys and passwords. + /// + internal static class CryptoKeyGenerator + { +#if NET451 + private const int DefaultPasswordLength = 16; + private const int GuidLength = 16; +#endif + + /// + /// Size of the SHA 512 key. + /// + internal const int Sha512KeySize = 64; + + /// + /// Generate a key with a specified key size. + /// + /// The size of the key. + /// Byte array representing the key. + internal static byte[] GenerateKeyBytes(int keySize) + { +#if NET451 + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = new RNGCryptoServiceProvider(); + cyptoProvider.GetNonZeroBytes(keyBytes); +#else + byte[] keyBytes = new byte[keySize]; + using var cyptoProvider = RandomNumberGenerator.Create(); + while (keyBytes.Contains(byte.MinValue)) + { + cyptoProvider.GetBytes(keyBytes); + } +#endif + return keyBytes; + } + + /// + /// Generates a key of the specified size. + /// + /// Desired key size. + /// A generated key. + internal static string GenerateKey(int keySize) + { + return Convert.ToBase64String(GenerateKeyBytes(keySize)); + } + } +} diff --git a/iothub/service/tests/DeviceAuthenticationTests.cs b/iothub/service/tests/DeviceAuthenticationTests.cs index ac933dc3d7..4c2bf8531c 100644 --- a/iothub/service/tests/DeviceAuthenticationTests.cs +++ b/iothub/service/tests/DeviceAuthenticationTests.cs @@ -7,7 +7,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.Devices.Common; +using Microsoft.Azure.Devices.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; From 03b7c49e3cee1fa1d302f16653b5258a03aa131b Mon Sep 17 00:00:00 2001 From: Roman Marusyk Date: Wed, 6 Oct 2021 01:14:08 +0300 Subject: [PATCH 74/77] Add XML docs for exceptions that can be thrown by ModuleClient.SendEventAsync and DeviceClient.SendEventAsync (#2178) * Add XML docs for exceptions that can be thrown by ModuleClient.SendEventAsync and DeviceClient.SendEventAsync * Add more XML docs for exceptions for ModuleClient.SendEventAsync and DeviceClient.SendEventAsync * Update iothub/device/src/DeviceClient.cs Co-authored-by: David R. Williamson * Add using for exceptions Co-authored-by: David R. Williamson --- iothub/device/src/DeviceClient.cs | 37 +++++++++++++++- iothub/device/src/ModuleClient.cs | 72 +++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/iothub/device/src/DeviceClient.cs b/iothub/device/src/DeviceClient.cs index d6179291ce..70a15c7e62 100644 --- a/iothub/device/src/DeviceClient.cs +++ b/iothub/device/src/DeviceClient.cs @@ -4,8 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net.Sockets; +using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using DotNetty.Transport.Channels; +using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Client.Transport; using Microsoft.Azure.Devices.Shared; @@ -475,6 +479,22 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// Sends an event to a hub /// /// The message to send. Should be disposed after sending. + /// Thrown when a required parameter is null. + /// Thrown if the service does not respond to the request within the timeout specified for the operation. + /// The timeout values are largely transport protocol specific. Check the corresponding transport settings to see if they can be configured. + /// The operation timeout for the client can be set using . + /// Thrown if the client encounters a transient retryable exception. + /// Thrown if a socket error occurs. + /// Thrown if an error occurs when performing an operation on a WebSocket connection. + /// Thrown if an I/O error occurs. + /// Thrown if the MQTT transport layer closes unexpectedly. + /// Thrown if an error occurs when communicating with IoT Hub service. + /// If is set to true then it is a transient exception. + /// If is set to false then it is a non-transient exception. + /// + /// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly. + /// Please note that the list of exceptions is not exhaustive. + /// /// The task to await public Task SendEventAsync(Message message) => InternalClient.SendEventAsync(message); @@ -483,7 +503,22 @@ public Task SetReceiveMessageHandlerAsync(ReceiveMessageCallback messageHandler, /// /// The message to send. Should be disposed after sending. /// A cancellation token to cancel the operation. - /// Thrown when the operation has been canceled. + /// Thrown when a required parameter is null. + /// Thrown if the service does not respond to the request before the expiration of the passed . + /// If a cancellation token is not supplied to the operation call, a cancellation token with an expiration time of 4 minutes is used. + /// + /// Thrown if the client encounters a transient retryable exception. + /// Thrown if a socket error occurs. + /// Thrown if an error occurs when performing an operation on a WebSocket connection. + /// Thrown if an I/O error occurs. + /// Thrown if the MQTT transport layer closes unexpectedly. + /// Thrown if an error occurs when communicating with IoT Hub service. + /// If is set to true then it is a transient exception. + /// If is set to false then it is a non-transient exception. + /// + /// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly. + /// Please note that the list of exceptions is not exhaustive. + /// /// The task to await public Task SendEventAsync(Message message, CancellationToken cancellationToken) => InternalClient.SendEventAsync(message, cancellationToken); diff --git a/iothub/device/src/ModuleClient.cs b/iothub/device/src/ModuleClient.cs index 8970dd1f0d..687406ee23 100644 --- a/iothub/device/src/ModuleClient.cs +++ b/iothub/device/src/ModuleClient.cs @@ -4,14 +4,19 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Net; using System.Net.Http; using System.Net.Security; +using System.Net.Sockets; +using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; +using DotNetty.Transport.Channels; using Microsoft.Azure.Devices.Client.Edge; +using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Client.Extensions; using Microsoft.Azure.Devices.Client.Transport; using Microsoft.Azure.Devices.Shared; @@ -346,6 +351,22 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// Sends an event to IoT hub /// /// The message. + /// Thrown when a required parameter is null. + /// Thrown if the service does not respond to the request within the timeout specified for the operation. + /// The timeout values are largely transport protocol specific. Check the corresponding transport settings to see if they can be configured. + /// The operation timeout for the client can be set using . + /// Thrown if the client encounters a transient retryable exception. + /// Thrown if a socket error occurs. + /// Thrown if an error occurs when performing an operation on a WebSocket connection. + /// Thrown if an I/O error occurs. + /// Thrown if the MQTT transport layer closes unexpectedly. + /// Thrown if an error occurs when communicating with IoT Hub service. + /// If is set to true then it is a transient exception. + /// If is set to false then it is a non-transient exception. + /// + /// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly. + /// Please note that the list of exceptions is not exhaustive. + /// /// The message containing the event public Task SendEventAsync(Message message) => InternalClient.SendEventAsync(message); @@ -354,7 +375,22 @@ public void SetRetryPolicy(IRetryPolicy retryPolicy) /// /// The message. /// A cancellation token to cancel the operation. - /// Thrown when the operation has been canceled. + /// Thrown when a required parameter is null. + /// Thrown if the service does not respond to the request before the expiration of the passed . + /// If a cancellation token is not supplied to the operation call, a cancellation token with an expiration time of 4 minutes is used. + /// + /// Thrown if the client encounters a transient retryable exception. + /// Thrown if a socket error occurs. + /// Thrown if an error occurs when performing an operation on a WebSocket connection. + /// Thrown if an I/O error occurs. + /// Thrown if the MQTT transport layer closes unexpectedly. + /// Thrown if an error occurs when communicating with IoT Hub service. + /// If is set to true then it is a transient exception. + /// If is set to false then it is a non-transient exception. + /// + /// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly. + /// Please note that the list of exceptions is not exhaustive. + /// /// The message containing the event public Task SendEventAsync(Message message, CancellationToken cancellationToken) => InternalClient.SendEventAsync(message, cancellationToken); @@ -521,7 +557,22 @@ public Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties, Can /// /// The output target for sending the given message /// The message to send - /// Thrown when the operation has been canceled. + /// Thrown when a required parameter is null. + /// Thrown if the service does not respond to the request within the timeout specified for the operation. + /// The timeout values are largely transport protocol specific. Check the corresponding transport settings to see if they can be configured. + /// The operation timeout for the client can be set using . + /// Thrown if the client encounters a transient retryable exception. + /// Thrown if a socket error occurs. + /// Thrown if an error occurs when performing an operation on a WebSocket connection. + /// Thrown if an I/O error occurs. + /// Thrown if the MQTT transport layer closes unexpectedly. + /// Thrown if an error occurs when communicating with IoT Hub service. + /// If is set to true then it is a transient exception. + /// If is set to false then it is a non-transient exception. + /// + /// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly. + /// Please note that the above list is not exhaustive. + /// /// The message containing the event public Task SendEventAsync(string outputName, Message message) => InternalClient.SendEventAsync(outputName, message); @@ -532,7 +583,22 @@ public Task SendEventAsync(string outputName, Message message) => /// The output target for sending the given message /// The message to send /// A cancellation token to cancel the operation. - /// Thrown when the operation has been canceled. + /// Thrown when a required parameter is null. + /// Thrown if the service does not respond to the request before the expiration of the passed . + /// If a cancellation token is not supplied to the operation call, a cancellation token with an expiration time of 4 minutes is used. + /// + /// Thrown if the client encounters a transient retryable exception. + /// Thrown if a socket error occurs. + /// Thrown if an error occurs when performing an operation on a WebSocket connection. + /// Thrown if an I/O error occurs. + /// Thrown if the MQTT transport layer closes unexpectedly. + /// Thrown if an error occurs when communicating with IoT Hub service. + /// If is set to true then it is a transient exception. + /// If is set to false then it is a non-transient exception. + /// + /// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly. + /// Please note that the above list is not exhaustive. + /// /// The message containing the event public Task SendEventAsync(string outputName, Message message, CancellationToken cancellationToken) => InternalClient.SendEventAsync(outputName, message, cancellationToken); From 900717aef539ff1cebf6c5d459d228e24cae2d90 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 7 Oct 2021 14:30:03 -0700 Subject: [PATCH 75/77] refactor(iot-device): Merge flows for twin and client property operations (#2180) --- e2e/test/iothub/command/CommandE2ETests.cs | 28 +-- .../iothub/properties/PropertiesE2ETests.cs | 208 ++++++---------- .../PropertiesFaultInjectionTests.cs | 66 +++--- .../PropertiesWithComponentsE2ETests.cs | 222 ++++++------------ .../iothub/telemetry/TelemetrySendE2ETests.cs | 33 ++- iothub/device/src/ClientProperties.cs | 2 +- .../DeviceClient.ConventionBasedOperations.cs | 2 +- iothub/device/src/IDelegatingHandler.cs | 11 +- ...nternalClient.ConventionBasedOperations.cs | 15 +- iothub/device/src/InternalClient.cs | 34 +-- .../ModuleClient.ConventionBasedOperations.cs | 2 +- .../Transport/Amqp/AmqpConnectionHolder.cs | 9 +- .../src/Transport/Amqp/AmqpConnectionPool.cs | 7 +- .../Transport/Amqp/AmqpTransportHandler.cs | 141 +++++++---- iothub/device/src/Transport/Amqp/AmqpUnit.cs | 16 +- .../src/Transport/Amqp/AmqpUnitManager.cs | 7 +- .../src/Transport/Amqp/IAmqpUnitManager.cs | 3 +- .../src/Transport/AmqpIot/AmqpIotConstants.cs | 1 + .../Transport/AmqpIot/AmqpIotReceivingLink.cs | 42 +--- .../Transport/AmqpIot/AmqpIotSendingLink.cs | 7 +- .../src/Transport/DefaultDelegatingHandler.cs | 21 +- .../src/Transport/ErrorDelegatingHandler.cs | 18 +- .../src/Transport/HttpTransportHandler.cs | 13 +- .../Transport/Mqtt/MqttTransportHandler.cs | 83 +------ .../src/Transport/RetryDelegatingHandler.cs | 61 +---- .../device/tests/DeviceClientTwinApiTests.cs | 29 ++- .../tests/Mqtt/MqttTransportHandlerTests.cs | 30 ++- 27 files changed, 419 insertions(+), 692 deletions(-) diff --git a/e2e/test/iothub/command/CommandE2ETests.cs b/e2e/test/iothub/command/CommandE2ETests.cs index e1f00fcc26..8be5c30240 100644 --- a/e2e/test/iothub/command/CommandE2ETests.cs +++ b/e2e/test/iothub/command/CommandE2ETests.cs @@ -39,27 +39,23 @@ public class CommandE2ETests : E2EMsTestBase private static readonly TimeSpan s_defaultCommandTimeoutMinutes = TimeSpan.FromMinutes(1); [LoggedTestMethod] - public async Task Command_DeviceReceivesCommandAndResponse_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Command_DeviceReceivesCommandAndResponse(Client.TransportType transportType) { - await SendCommandAndRespondAsync(Client.TransportType.Mqtt_Tcp_Only, SetDeviceReceiveCommandAsync).ConfigureAwait(false); + await SendCommandAndRespondAsync(transportType, SetDeviceReceiveCommandAsync).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Command_DeviceReceivesCommandAndResponse_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Command_DeviceReceivesCommandAndResponseWithComponent(Client.TransportType transportType) { - await SendCommandAndRespondAsync(Client.TransportType.Mqtt_WebSocket_Only, SetDeviceReceiveCommandAsync).ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Command_DeviceReceivesCommandAndResponseWithComponent_Mqtt() - { - await SendCommandAndRespondAsync(Client.TransportType.Mqtt_Tcp_Only, SetDeviceReceiveCommandAsync, withComponent: true).ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Command_DeviceReceivesCommandAndResponseWithComponent_MqttWs() - { - await SendCommandAndRespondAsync(Client.TransportType.Mqtt_WebSocket_Only, SetDeviceReceiveCommandAsync, withComponent: true).ConfigureAwait(false); + await SendCommandAndRespondAsync(transportType, SetDeviceReceiveCommandAsync, withComponent: true).ConfigureAwait(false); } public static async Task DigitalTwinsSendCommandAndVerifyResponseAsync(string deviceId, string componentName, string commandName, MsTestLogger logger) diff --git a/e2e/test/iothub/properties/PropertiesE2ETests.cs b/e2e/test/iothub/properties/PropertiesE2ETests.cs index 13d907c604..a441a3adf6 100644 --- a/e2e/test/iothub/properties/PropertiesE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesE2ETests.cs @@ -33,189 +33,113 @@ public class PropertiesE2ETests : E2EMsTestBase }; [LoggedTestMethod] - public async Task Properties_DeviceSetsPropertyAndGetsItBack_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceSetsPropertyAndGetsItBack(Client.TransportType transportType) { - await Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_DeviceSetsPropertyAndGetsItBack_MqttWs() - { - await Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_DeviceSetsPropertyMapAndGetsItBack_Mqtt() - { - await Properties_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_DeviceSetsPropertyMapAndGetsItBack_MqttWs() - { - await Properties_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes_Mqtt() - { - await Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes( - Client.TransportType.Mqtt_Tcp_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes_MqttWs() - { - await Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes( - Client.TransportType.Mqtt_WebSocket_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEvent_Mqtt() - { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_Tcp_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEvent_MqttWs() - { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_WebSocket_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); + await Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceSetsPropertyMapAndGetsItBack(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_Tcp_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); + await Properties_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_WebSocket_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); + await Properties_ServiceSetsWritablePropertyAndDeviceUnsubscribes(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEvent(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_Tcp_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_WebSocket_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync(transportType, s_mapOfPropertyValues).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_Tcp_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_WebSocket_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync(transportType, s_mapOfPropertyValues).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet(Client.TransportType transportType) { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_MqttWs() - { - await Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await Properties_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_DeviceSetsPropertyAndServiceReceivesIt_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceSetsPropertyAndServiceReceivesIt(Client.TransportType transportType) { - await Properties_DeviceSetsPropertyAndServiceReceivesItAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); + await Properties_DeviceSetsPropertyAndServiceReceivesItAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_DeviceSetsPropertyAndServiceReceivesIt_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingIt(Client.TransportType transportType) { - await Properties_DeviceSetsPropertyAndServiceReceivesItAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingIt_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_ClientHandlesRejectionInvalidPropertyName_Mqtt(Client.TransportType transportType) { - await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingIt_MqttWs() - { - await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ClientHandlesRejectionInvalidPropertyName_Mqtt() - { - await Properties_ClientHandlesRejectionInvalidPropertyNameAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_ClientHandlesRejectionInvalidPropertyName_MqttWs() - { - await Properties_ClientHandlesRejectionInvalidPropertyNameAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await Properties_ClientHandlesRejectionInvalidPropertyNameAsync(transportType).ConfigureAwait(false); } private async Task Properties_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport) diff --git a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs index 4016dff7df..720bc228dd 100644 --- a/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs +++ b/e2e/test/iothub/properties/PropertiesFaultInjectionTests.cs @@ -22,10 +22,14 @@ public class PropertiesFaultInjectionTests : E2EMsTestBase private static readonly string s_devicePrefix = $"E2E_{nameof(PropertiesFaultInjectionTests)}_"; [LoggedTestMethod] - public async Task Properties_DeviceUpdateClientPropertiesTcpConnRecovery_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceUpdateClientPropertiesTcpConnRecovery(Client.TransportType transportType) { await Properties_DeviceUpdateClientPropertiesRecoveryAsync( - Client.TransportType.Mqtt_Tcp_Only, + transportType, FaultInjection.FaultType_Tcp, FaultInjection.FaultCloseReason_Boom, FaultInjection.DefaultFaultDelay) @@ -33,21 +37,12 @@ await Properties_DeviceUpdateClientPropertiesRecoveryAsync( } [LoggedTestMethod] - public async Task Properties_DeviceUpdateClientPropertiesTcpConnRecovery_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + public async Task Properties_DeviceUpdateClientPropertiesGracefulShutdownRecovery_Mqtt(Client.TransportType transportType) { await Properties_DeviceUpdateClientPropertiesRecoveryAsync( - Client.TransportType.Mqtt_WebSocket_Only, - FaultInjection.FaultType_Tcp, - FaultInjection.FaultCloseReason_Boom, - FaultInjection.DefaultFaultDelay) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_DeviceUpdateClientPropertiesGracefulShutdownRecovery_Mqtt() - { - await Properties_DeviceUpdateClientPropertiesRecoveryAsync( - Client.TransportType.Mqtt_Tcp_Only, + transportType, FaultInjection.FaultType_GracefulShutdownMqtt, FaultInjection.FaultCloseReason_Bye, FaultInjection.DefaultFaultDelay) @@ -55,21 +50,27 @@ await Properties_DeviceUpdateClientPropertiesRecoveryAsync( } [LoggedTestMethod] - public async Task Properties_DeviceUpdateClientPropertiesGracefulShutdownRecovery_MqttWs() + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceUpdateClientPropertiesGracefulShutdownRecovery_Amqp(Client.TransportType transportType) { await Properties_DeviceUpdateClientPropertiesRecoveryAsync( - Client.TransportType.Mqtt_WebSocket_Only, - FaultInjection.FaultType_GracefulShutdownMqtt, + transportType, + FaultInjection.FaultType_GracefulShutdownAmqp, FaultInjection.FaultCloseReason_Bye, FaultInjection.DefaultFaultDelay) .ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_DeviceReceivePropertyUpdateTcpConnRecovery_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceReceivePropertyUpdateTcpConnRecovery(Client.TransportType transportType) { await Properties_DeviceReceivePropertyUpdateRecoveryAsync( - Client.TransportType.Mqtt_Tcp_Only, + transportType, FaultInjection.FaultType_Tcp, FaultInjection.FaultCloseReason_Boom, FaultInjection.DefaultFaultDelay) @@ -77,21 +78,12 @@ await Properties_DeviceReceivePropertyUpdateRecoveryAsync( } [LoggedTestMethod] - public async Task Properties_DeviceReceivePropertyUpdateTcpConnRecovery_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + public async Task Properties_DeviceReceivePropertyUpdateGracefulShutdownRecovery_Mqtt(Client.TransportType transportType) { await Properties_DeviceReceivePropertyUpdateRecoveryAsync( - Client.TransportType.Mqtt_WebSocket_Only, - FaultInjection.FaultType_Tcp, - FaultInjection.FaultCloseReason_Boom, - FaultInjection.DefaultFaultDelay) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Properties_DeviceReceivePropertyUpdateGracefulShutdownRecovery_Mqtt() - { - await Properties_DeviceReceivePropertyUpdateRecoveryAsync( - Client.TransportType.Mqtt_Tcp_Only, + transportType, FaultInjection.FaultType_GracefulShutdownMqtt, FaultInjection.FaultCloseReason_Bye, FaultInjection.DefaultFaultDelay) @@ -99,11 +91,13 @@ await Properties_DeviceReceivePropertyUpdateRecoveryAsync( } [LoggedTestMethod] - public async Task Properties_DeviceReceivePropertyUpdateGracefulShutdownRecovery_MqttWs() + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceReceivePropertyUpdateGracefulShutdownRecovery_Amqp(Client.TransportType transportType) { await Properties_DeviceReceivePropertyUpdateRecoveryAsync( - Client.TransportType.Mqtt_WebSocket_Only, - FaultInjection.FaultType_GracefulShutdownMqtt, + transportType, + FaultInjection.FaultType_GracefulShutdownAmqp, FaultInjection.FaultCloseReason_Bye, FaultInjection.DefaultFaultDelay) .ConfigureAwait(false); diff --git a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs index 1ce6d0fb7e..3f0869e4c6 100644 --- a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs @@ -35,205 +35,131 @@ public class PropertiesWithComponentsE2ETests : E2EMsTestBase }; [LoggedTestMethod] - public async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBack_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBack(Client.TransportType transportType) { - await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBack_MqttWs() - { - await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBack_Mqtt() - { - await PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBack_MqttWs() - { - await PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes_Mqtt() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes( - Client.TransportType.Mqtt_Tcp_Only, - Guid.NewGuid()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes_MqttWs() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes( - Client.TransportType.Mqtt_WebSocket_Only, - Guid.NewGuid()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEvent_Mqtt() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_Tcp_Only, - Guid.NewGuid()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEvent_MqttWs() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_WebSocket_Only, - Guid.NewGuid()) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_Mqtt() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_Tcp_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent_MqttWs() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync( - Client.TransportType.Mqtt_WebSocket_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_Mqtt() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBack(Client.TransportType transportType) { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(transportType, s_mapOfPropertyValues).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesIt_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes(Client.TransportType transportType) { - await PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesItAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesIt_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceUnsubscribes(Client.TransportType transportType) { - await PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesItAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceUnsubscribes(transportType, s_mapOfPropertyValues).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEvent(Client.TransportType transportType) { - await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEvent(Client.TransportType transportType) { - await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAsync(transportType, s_mapOfPropertyValues).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyName_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGet(Client.TransportType transportType) { - await PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync( - Client.TransportType.Mqtt_Tcp_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesItOnNextGetAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyName_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesIt(Client.TransportType transportType) { - await PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync( - Client.TransportType.Mqtt_WebSocket_Only) - .ConfigureAwait(false); + await PropertiesWithComponents_DeviceSetsPropertyAndServiceReceivesItAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingIt(Client.TransportType transportType) { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_Tcp_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); + await Properties_DeviceSendsNullValueForPropertyResultsServiceRemovingItAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyName(Client.TransportType transportType) { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_WebSocket_Only, - Guid.NewGuid().ToString()) - .ConfigureAwait(false); + await PropertiesWithComponents_ClientHandlesRejectionInvalidPropertyNameAsync(transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndResponds(Client.TransportType transportType) { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_Tcp_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync(transportType, Guid.NewGuid().ToString()).ConfigureAwait(false); } [LoggedTestMethod] - public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds_MqttWs() - { - await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync( - Client.TransportType.Mqtt_WebSocket_Only, - s_mapOfPropertyValues) - .ConfigureAwait(false); - } - - private async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport) + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task PropertiesWithComponents_ServiceSetsWritablePropertyMapAndDeviceReceivesEventAndResponds(Client.TransportType transportType) { - TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); - using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); - - await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString(), Logger).ConfigureAwait(false); + await PropertiesWithComponents_ServiceSetsWritablePropertyAndDeviceReceivesEventAndRespondsAsync(transportType, s_mapOfPropertyValues).ConfigureAwait(false); } - private async Task PropertiesWithComponents_DeviceSetsPropertyMapAndGetsItBackSingleDeviceAsync(Client.TransportType transport) + private async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackSingleDeviceAsync(Client.TransportType transport, T propValue) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); - await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, s_mapOfPropertyValues, Logger).ConfigureAwait(false); + await PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, propValue, Logger).ConfigureAwait(false); } public static async Task PropertiesWithComponents_DeviceSetsPropertyAndGetsItBackAsync(DeviceClient deviceClient, string deviceId, T propValue, MsTestLogger logger) diff --git a/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs b/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs index 5eed7aab2c..fac19320d7 100644 --- a/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs +++ b/e2e/test/iothub/telemetry/TelemetrySendE2ETests.cs @@ -19,27 +19,23 @@ public partial class TelemetrySendE2ETests : E2EMsTestBase private readonly string ModulePrefix = $"{nameof(TelemetrySendE2ETests)}_"; [LoggedTestMethod] - public async Task Telemetry_DeviceSendSingleTelemetry_Mqtt() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Telemetry_DeviceSendSingleTelemetry(Client.TransportType transportType) { - await SendTelemetryAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_Tcp_Only).ConfigureAwait(false); + await SendTelemetryAsync(TestDeviceType.Sasl, transportType).ConfigureAwait(false); } [LoggedTestMethod] - public async Task Telemetry_DeviceSendSingleTelemetry_MqttWs() + [DataRow(Client.TransportType.Mqtt_Tcp_Only)] + [DataRow(Client.TransportType.Mqtt_WebSocket_Only)] + [DataRow(Client.TransportType.Amqp_Tcp_Only)] + [DataRow(Client.TransportType.Amqp_WebSocket_Only)] + public async Task Telemetry_DeviceSendSingleTelemetryWithComponent(Client.TransportType transportType) { - await SendTelemetryAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_WebSocket_Only).ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Telemetry_DeviceSendSingleTelemetryWithComponent_Mqtt() - { - await SendTelemetryWithComponentAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_Tcp_Only).ConfigureAwait(false); - } - - [LoggedTestMethod] - public async Task Telemetry_DeviceSendSingleTelemetryWithComponent_MqttWs() - { - await SendTelemetryWithComponentAsync(TestDeviceType.Sasl, Client.TransportType.Mqtt_WebSocket_Only).ConfigureAwait(false); + await SendTelemetryWithComponentAsync(TestDeviceType.Sasl, transportType).ConfigureAwait(false); } private async Task SendTelemetryAsync(TestDeviceType type, Client.TransportType transport) @@ -84,7 +80,7 @@ public static async Task SendSingleMessageWithComponentAsync(DeviceClient device } } - public static (Client.TelemetryMessage message, string p1Value) ComposeTelemetryMessageWithComponent(MsTestLogger logger) + public static (TelemetryMessage message, string p1Value) ComposeTelemetryMessageWithComponent(MsTestLogger logger) { string messageId = Guid.NewGuid().ToString(); string p1Value = Guid.NewGuid().ToString(); @@ -106,7 +102,7 @@ public static (Client.TelemetryMessage message, string p1Value) ComposeTelemetry return (message, p1Value); } - public static (Client.TelemetryMessage message, string p1Value) ComposeTelemetryMessage(MsTestLogger logger) + public static (TelemetryMessage message, string p1Value) ComposeTelemetryMessage(MsTestLogger logger) { string messageId = Guid.NewGuid().ToString(); string p1Value = Guid.NewGuid().ToString(); @@ -125,6 +121,5 @@ public static (Client.TelemetryMessage message, string p1Value) ComposeTelemetry return (message, p1Value); } - } } diff --git a/iothub/device/src/ClientProperties.cs b/iothub/device/src/ClientProperties.cs index 43d708f32d..31c8fe4966 100644 --- a/iothub/device/src/ClientProperties.cs +++ b/iothub/device/src/ClientProperties.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.Devices.Client /// /// /// The class is not meant to be constructed by customer code. - /// It is intended to be returned fully populated from the client method . + /// It is intended to be returned fully populated from the internal client method . /// public class ClientProperties { diff --git a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs index 74619e6d26..9331bb8f26 100644 --- a/iothub/device/src/DeviceClient.ConventionBasedOperations.cs +++ b/iothub/device/src/DeviceClient.ConventionBasedOperations.cs @@ -49,7 +49,7 @@ public Task SubscribeToCommandsAsync( /// A cancellation token to cancel the operation. /// The device properties. public Task GetClientPropertiesAsync(CancellationToken cancellationToken = default) - => InternalClient.GetClientPropertiesAsync(cancellationToken); + => InternalClient.GetClientTwinPropertiesAsync(cancellationToken); /// /// Update the client properties. diff --git a/iothub/device/src/IDelegatingHandler.cs b/iothub/device/src/IDelegatingHandler.cs index 0651bd1bd3..5f3e703f26 100644 --- a/iothub/device/src/IDelegatingHandler.cs +++ b/iothub/device/src/IDelegatingHandler.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Shared; @@ -59,18 +60,12 @@ internal interface IDelegatingHandler : IContinuationProvider SendTwinGetAsync(CancellationToken cancellationToken); + Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken); - Task SendTwinPatchAsync(TwinCollection reportedProperties, CancellationToken cancellationToken); + Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken); Task EnableTwinPatchAsync(CancellationToken cancellationToken); Task DisableTwinPatchAsync(CancellationToken cancellationToken); - - // Convention driven operations. - - Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken); - - Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken); } } diff --git a/iothub/device/src/InternalClient.ConventionBasedOperations.cs b/iothub/device/src/InternalClient.ConventionBasedOperations.cs index 8b7341565a..a8e19cb9cc 100644 --- a/iothub/device/src/InternalClient.ConventionBasedOperations.cs +++ b/iothub/device/src/InternalClient.ConventionBasedOperations.cs @@ -2,11 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Shared; +using Newtonsoft.Json; namespace Microsoft.Azure.Devices.Client { @@ -64,11 +66,15 @@ internal Task SubscribeToCommandsAsync(Func GetClientPropertiesAsync(CancellationToken cancellationToken) + internal async Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { try { - return await InnerHandler.GetClientPropertiesAsync(PayloadConvention, cancellationToken).ConfigureAwait(false); + ClientPropertiesAsDictionary clientPropertiesDictionary = await InnerHandler + .GetClientTwinPropertiesAsync(cancellationToken) + .ConfigureAwait(false); + + return clientPropertiesDictionary.ToClientProperties(PayloadConvention); } catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) { @@ -87,7 +93,10 @@ internal async Task UpdateClientPropertiesAsync( try { clientProperties.Convention = PayloadConvention; - return await InnerHandler.SendPropertyPatchAsync(clientProperties, cancellationToken).ConfigureAwait(false); + byte[] body = clientProperties.GetPayloadObjectBytes(); + using Stream bodyStream = new MemoryStream(body); + + return await InnerHandler.SendClientTwinPropertyPatchAsync(bodyStream, cancellationToken).ConfigureAwait(false); } catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) { diff --git a/iothub/device/src/InternalClient.cs b/iothub/device/src/InternalClient.cs index b583802eb9..d729b949f8 100644 --- a/iothub/device/src/InternalClient.cs +++ b/iothub/device/src/InternalClient.cs @@ -13,6 +13,8 @@ using System.IO; using Microsoft.Azure.Devices.Client.Exceptions; using System.ComponentModel; +using System.Text; +using Newtonsoft.Json; #if NET451 @@ -1167,18 +1169,14 @@ public async Task GetTwinAsync() /// For the complete device twin object, use Microsoft.Azure.Devices.RegistryManager.GetTwinAsync(string deviceId). /// /// The device twin object for the current device - public Task GetTwinAsync(CancellationToken cancellationToken) + public async Task GetTwinAsync(CancellationToken cancellationToken) { - // `GetTwinAsync` shall call `SendTwinGetAsync` on the transport to get the twin state. - try - { - return InnerHandler.SendTwinGetAsync(cancellationToken); - } - catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) + TwinProperties twinProperties = await InnerHandler + .GetClientTwinPropertiesAsync(cancellationToken).ConfigureAwait(false); + return new Twin { - cancellationToken.ThrowIfCancellationRequested(); - throw; - } + Properties = twinProperties, + }; } /// @@ -1205,7 +1203,7 @@ public async Task UpdateReportedPropertiesAsync(TwinCollection reportedPropertie /// /// Reported properties to push /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - public Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) + public async Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) { // `UpdateReportedPropertiesAsync` shall throw an `ArgumentNull` exception if `reportedProperties` is null. if (reportedProperties == null) @@ -1213,16 +1211,10 @@ public Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties, Can throw new ArgumentNullException(nameof(reportedProperties)); } - // `UpdateReportedPropertiesAsync` shall call `SendTwinPatchAsync` on the transport to update the reported properties. - try - { - return InnerHandler.SendTwinPatchAsync(reportedProperties, cancellationToken); - } - catch (IotHubCommunicationException ex) when (ex.InnerException is OperationCanceledException) - { - cancellationToken.ThrowIfCancellationRequested(); - throw; - } + string body = JsonConvert.SerializeObject(reportedProperties); + using Stream bodyStream = new MemoryStream(Encoding.UTF8.GetBytes(body)); + + await InnerHandler.SendClientTwinPropertyPatchAsync(bodyStream, cancellationToken).ConfigureAwait(false); } // Codes_SRS_DEVICECLIENT_18_005: When a patch is received from the service, the `callback` shall be called. diff --git a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs index be6e9adc53..231a578abd 100644 --- a/iothub/device/src/ModuleClient.ConventionBasedOperations.cs +++ b/iothub/device/src/ModuleClient.ConventionBasedOperations.cs @@ -49,7 +49,7 @@ public Task SubscribeToCommandsAsync( /// A cancellation token to cancel the operation. /// The device properties. public Task GetClientPropertiesAsync(CancellationToken cancellationToken = default) - => InternalClient.GetClientPropertiesAsync(cancellationToken); + => InternalClient.GetClientTwinPropertiesAsync(cancellationToken); /// /// Update the client properties. diff --git a/iothub/device/src/Transport/Amqp/AmqpConnectionHolder.cs b/iothub/device/src/Transport/Amqp/AmqpConnectionHolder.cs index c82660aed5..6aefe5494c 100644 --- a/iothub/device/src/Transport/Amqp/AmqpConnectionHolder.cs +++ b/iothub/device/src/Transport/Amqp/AmqpConnectionHolder.cs @@ -2,13 +2,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; +using Microsoft.Azure.Amqp; using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Client.Extensions; using Microsoft.Azure.Devices.Client.Transport.AmqpIot; -using System.Collections.Generic; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client.Transport.Amqp { @@ -36,7 +37,7 @@ public AmqpConnectionHolder(DeviceIdentity deviceIdentity) public AmqpUnit CreateAmqpUnit( DeviceIdentity deviceIdentity, Func onMethodCallback, - Action twinMessageListener, + Action twinMessageListener, Func onModuleMessageReceivedCallback, Func onDeviceMessageReceivedCallback, Action onUnitDisconnected) @@ -275,4 +276,4 @@ internal DeviceIdentity GetDeviceIdentityOfAuthenticationProvider() return _deviceIdentity; } } -} \ No newline at end of file +} diff --git a/iothub/device/src/Transport/Amqp/AmqpConnectionPool.cs b/iothub/device/src/Transport/Amqp/AmqpConnectionPool.cs index caffb063b4..d682ffc51a 100644 --- a/iothub/device/src/Transport/Amqp/AmqpConnectionPool.cs +++ b/iothub/device/src/Transport/Amqp/AmqpConnectionPool.cs @@ -4,9 +4,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Azure.Devices.Client.Transport.AmqpIot; +using Microsoft.Azure.Amqp; using Microsoft.Azure.Devices.Client.Exceptions; +using Microsoft.Azure.Devices.Client.Transport.AmqpIot; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client.Transport.Amqp { @@ -19,7 +20,7 @@ internal class AmqpConnectionPool : IAmqpUnitManager public AmqpUnit CreateAmqpUnit( DeviceIdentity deviceIdentity, Func onMethodCallback, - Action twinMessageListener, + Action twinMessageListener, Func onModuleMessageReceivedCallback, Func onDeviceMessageReceivedCallback, Action onUnitDisconnected) diff --git a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs index 11fefb6d12..33342bd310 100644 --- a/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs +++ b/iothub/device/src/Transport/Amqp/AmqpTransportHandler.cs @@ -4,11 +4,15 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.Amqp; using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Client.Transport.AmqpIot; using Microsoft.Azure.Devices.Shared; +using Newtonsoft.Json; namespace Microsoft.Azure.Devices.Client.Transport.Amqp { @@ -21,7 +25,7 @@ internal class AmqpTransportHandler : TransportHandler private readonly AmqpUnit _amqpUnit; private readonly Action _onDesiredStatePatchListener; private readonly object _lock = new object(); - private ConcurrentDictionary> _twinResponseCompletions = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _twinResponseCompletions = new ConcurrentDictionary>(); private bool _closed; static AmqpTransportHandler() @@ -347,52 +351,80 @@ public override async Task DisableTwinPatchAsync(CancellationToken cancellationT } } - public override async Task SendTwinGetAsync(CancellationToken cancellationToken) + public override async Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { - Logging.Enter(this, cancellationToken, nameof(SendTwinGetAsync)); + Logging.Enter(this, cancellationToken, nameof(GetClientTwinPropertiesAsync)); try { await EnableTwinPatchAsync(cancellationToken).ConfigureAwait(false); - Twin twin = await RoundTripTwinMessageAsync(AmqpTwinMessageType.Get, null, cancellationToken) + AmqpMessage responseFromService = await RoundTripTwinMessageAsync(AmqpTwinMessageType.Get, null, cancellationToken) .ConfigureAwait(false); - return twin ?? throw new InvalidOperationException("Service rejected the message"); + + if (responseFromService == null) + { + throw new InvalidOperationException("Service rejected the message"); + } + + // We will use UTF-8 for decoding the service response. This is because UTF-8 is the only currently supported encoding format. + using var reader = new StreamReader(responseFromService.BodyStream, Encoding.UTF8); + string body = reader.ReadToEnd(); + + try + { + // We will use NewtonSoft Json to deserialize the service response to the appropriate type; i.e. Twin for non-convention-based operation + // and ClientProperties for convention-based operations. + return JsonConvert.DeserializeObject(body); + } + catch (JsonReaderException ex) + { + if (Logging.IsEnabled) + Logging.Error(this, $"Failed to parse Twin JSON: {ex}. Message body: '{body}'"); + + throw; + } } finally { - Logging.Exit(this, cancellationToken, nameof(SendTwinGetAsync)); + Logging.Exit(this, cancellationToken, nameof(GetClientTwinPropertiesAsync)); } } - public override async Task SendTwinPatchAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) + public override async Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken) { - Logging.Enter(this, reportedProperties, cancellationToken, nameof(SendTwinPatchAsync)); + Logging.Enter(this, reportedProperties, cancellationToken, nameof(SendClientTwinPropertyPatchAsync)); try { await EnableTwinPatchAsync(cancellationToken).ConfigureAwait(false); - await RoundTripTwinMessageAsync(AmqpTwinMessageType.Patch, reportedProperties, cancellationToken).ConfigureAwait(false); + AmqpMessage responseFromService = await RoundTripTwinMessageAsync(AmqpTwinMessageType.Patch, reportedProperties, cancellationToken).ConfigureAwait(false); + + long updatedVersion = GetVersion(responseFromService); + return new ClientPropertiesUpdateResponse + { + Version = updatedVersion, + }; } finally { - Logging.Exit(this, reportedProperties, cancellationToken, nameof(SendTwinPatchAsync)); + Logging.Exit(this, reportedProperties, cancellationToken, nameof(SendClientTwinPropertyPatchAsync)); } } - private async Task RoundTripTwinMessageAsync( + private async Task RoundTripTwinMessageAsync( AmqpTwinMessageType amqpTwinMessageType, - TwinCollection reportedProperties, + Stream reportedProperties, CancellationToken cancellationToken) { Logging.Enter(this, cancellationToken, nameof(RoundTripTwinMessageAsync)); string correlationId = amqpTwinMessageType + Guid.NewGuid().ToString(); - Twin response = null; + AmqpMessage response = default; try { cancellationToken.ThrowIfCancellationRequested(); - var taskCompletionSource = new TaskCompletionSource(); + var taskCompletionSource = new TaskCompletionSource(); _twinResponseCompletions[correlationId] = taskCompletionSource; await _amqpUnit.SendTwinMessageAsync(amqpTwinMessageType, correlationId, reportedProperties, _operationTimeout).ConfigureAwait(false); @@ -521,59 +553,64 @@ private async Task DisposeMessageAsync(string lockToken, AmqpIotDisposeActions o #endregion Accept-Dispose - #region Convention-based operations - - public override Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) - { - throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + - "Note that you can still retrieve a client's properties using DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + - "ModuleClient.GetTwinAsync(CancellationToken cancellationToken) operations, but the properties will not be formatted " + - "as per DTDL terminology."); - } - - public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) - { - throw new NotImplementedException("This operation is currently not supported over AMQP, please use MQTT protocol instead. " + - "Note that you can still retrieve a client's properties using DeviceClient.GetTwinAsync(CancellationToken cancellationToken) or " + - "ModuleClient.GetTwinAsync(CancellationToken cancellationToken) operations, but the properties will not be formatted " + - "as per DTDL terminology."); - } - - #endregion Convention-based operations - #region Helpers - private void TwinMessageListener(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default) + private void TwinMessageListener(AmqpMessage responseFromService, string correlationId, IotHubException ex = default) { if (correlationId == null) { - // This is desired property updates, so call the callback with TwinCollection. - _onDesiredStatePatchListener(twinCollection); + // This is desired property updates, so invoke the callback with TwinCollection. + using var reader = new StreamReader(responseFromService.BodyStream, Encoding.UTF8); + string responseBody = reader.ReadToEnd(); + + _onDesiredStatePatchListener(JsonConvert.DeserializeObject(responseBody)); } - else + else if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase) + || correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase)) { - if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase) - || correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase)) + Logging.Info(this, $"Received a response for operation with correlation Id {correlationId}.", nameof(TwinMessageListener)); + + // For Get and Patch, complete the task. + if (_twinResponseCompletions.TryRemove(correlationId, out TaskCompletionSource task)) { - // For Get and Patch, complete the task. - if (_twinResponseCompletions.TryRemove(correlationId, out TaskCompletionSource task)) + if (ex == default) { - if (ex == default) - { - task.SetResult(twin); - } - else - { - task.SetException(ex); - } + task.SetResult(responseFromService); } else { - // This can happen if we received a message from service with correlation Id that was not set by SDK or does not exist in dictionary. - Logging.Info("Could not remove correlation id to complete the task awaiter for a twin operation.", nameof(TwinMessageListener)); + task.SetException(ex); } } + else + { + // This can happen if we received a message from service with correlation Id that was not sent by SDK or does not exist in dictionary. + Logging.Error(this, $"Could not remove correlation id {correlationId} to complete the task awaiter for a twin operation.", nameof(TwinMessageListener)); + } + } + else if (correlationId.StartsWith(AmqpTwinMessageType.Put.ToString(), StringComparison.OrdinalIgnoreCase)) + { + // This is an acknowledgment received from service for subscribing to desired property updates. + Logging.Info(this, $"Subscribed for twin successfully with a correlation Id of {correlationId}.", nameof(TwinMessageListener)); } + else + { + // This can happen if we received a message from service with correlation Id that was not sent by SDK or does not exist in dictionary. + Logging.Error(this, $"Received an unexpected response from service with correlation Id {correlationId}.", nameof(TwinMessageListener)); + } + } + + internal static long GetVersion(AmqpMessage response) + { + if (response != null) + { + if (response.MessageAnnotations.Map.TryGetValue(AmqpIotConstants.ResponseVersionName, out long version)) + { + return version; + } + } + + return -1; } #endregion Helpers diff --git a/iothub/device/src/Transport/Amqp/AmqpUnit.cs b/iothub/device/src/Transport/Amqp/AmqpUnit.cs index fe09b63a40..833c6ab556 100644 --- a/iothub/device/src/Transport/Amqp/AmqpUnit.cs +++ b/iothub/device/src/Transport/Amqp/AmqpUnit.cs @@ -10,6 +10,8 @@ using Microsoft.Azure.Devices.Shared; using Microsoft.Azure.Devices.Client.Transport.Amqp; using Microsoft.Azure.Devices.Client.Exceptions; +using System.IO; +using Microsoft.Azure.Amqp; namespace Microsoft.Azure.Devices.Client.Transport.AmqpIot { @@ -19,7 +21,7 @@ internal class AmqpUnit : IDisposable private readonly DeviceIdentity _deviceIdentity; private readonly Func _onMethodCallback; - private readonly Action _twinMessageListener; + private readonly Action _twinMessageListener; private readonly Func _onModuleMessageReceivedCallback; private readonly Func _onDeviceMessageReceivedCallback; private readonly IAmqpConnectionHolder _amqpConnectionHolder; @@ -54,7 +56,7 @@ public AmqpUnit( DeviceIdentity deviceIdentity, IAmqpConnectionHolder amqpConnectionHolder, Func onMethodCallback, - Action twinMessageListener, + Action twinMessageListener, Func onModuleMessageReceivedCallback, Func onDeviceMessageReceivedCallback, Action onUnitDisconnected) @@ -747,21 +749,21 @@ private async Task OpenTwinSenderLinkAsync(AmqpIotSession amqpIotSession, string } } - private void OnDesiredPropertyReceived(Twin twin, string correlationId, TwinCollection twinCollection, IotHubException ex = default) + private void OnDesiredPropertyReceived(AmqpMessage responseFromService, string correlationId, IotHubException ex = default) { - Logging.Enter(this, twin, nameof(OnDesiredPropertyReceived)); + Logging.Enter(this, responseFromService, nameof(OnDesiredPropertyReceived)); try { - _twinMessageListener?.Invoke(twin, correlationId, twinCollection, ex); + _twinMessageListener?.Invoke(responseFromService, correlationId, ex); } finally { - Logging.Exit(this, twin, nameof(OnDesiredPropertyReceived)); + Logging.Exit(this, responseFromService, nameof(OnDesiredPropertyReceived)); } } - public async Task SendTwinMessageAsync(AmqpTwinMessageType amqpTwinMessageType, string correlationId, TwinCollection reportedProperties, TimeSpan timeout) + public async Task SendTwinMessageAsync(AmqpTwinMessageType amqpTwinMessageType, string correlationId, Stream reportedProperties, TimeSpan timeout) { Logging.Enter(this, timeout, nameof(SendTwinMessageAsync)); diff --git a/iothub/device/src/Transport/Amqp/AmqpUnitManager.cs b/iothub/device/src/Transport/Amqp/AmqpUnitManager.cs index f9d4723f84..a94dfe6829 100644 --- a/iothub/device/src/Transport/Amqp/AmqpUnitManager.cs +++ b/iothub/device/src/Transport/Amqp/AmqpUnitManager.cs @@ -4,9 +4,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Azure.Devices.Shared; -using Microsoft.Azure.Devices.Client.Transport.AmqpIot; +using Microsoft.Azure.Amqp; using Microsoft.Azure.Devices.Client.Exceptions; +using Microsoft.Azure.Devices.Client.Transport.AmqpIot; +using Microsoft.Azure.Devices.Shared; namespace Microsoft.Azure.Devices.Client.Transport.Amqp { @@ -30,7 +31,7 @@ internal static AmqpUnitManager GetInstance() public AmqpUnit CreateAmqpUnit( DeviceIdentity deviceIdentity, Func onMethodCallback, - Action twinMessageListener, + Action twinMessageListener, Func onModuleMessageReceivedCallback, Func onDeviceMessageReceivedCallback, Action onUnitDisconnected) diff --git a/iothub/device/src/Transport/Amqp/IAmqpUnitManager.cs b/iothub/device/src/Transport/Amqp/IAmqpUnitManager.cs index 509b5da002..09ffe1abc3 100644 --- a/iothub/device/src/Transport/Amqp/IAmqpUnitManager.cs +++ b/iothub/device/src/Transport/Amqp/IAmqpUnitManager.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using Microsoft.Azure.Amqp; using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Client.Transport.AmqpIot; using Microsoft.Azure.Devices.Shared; @@ -14,7 +15,7 @@ internal interface IAmqpUnitManager AmqpUnit CreateAmqpUnit( DeviceIdentity deviceIdentity, Func onMethodCallback, - Action twinMessageListener, + Action twinMessageListener, Func onModuleMessageReceivedCallback, Func onDeviceMessageReceivedCallback, Action onUnitDisconnected); diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotConstants.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotConstants.cs index aef7b0f559..d42f6dcc7d 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotConstants.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotConstants.cs @@ -27,6 +27,7 @@ internal static class AmqpIotConstants internal static readonly Accepted AcceptedOutcome = AmqpConstants.AcceptedOutcome; internal const string ResponseStatusName = "status"; + internal const string ResponseVersionName = "version"; internal const string TelemetrySenderLinkSuffix = "TelemetrySenderLink"; internal const string TelemetryReceiveLinkSuffix = "TelemetryReceiverLink"; internal const string EventsReceiverLinkSuffix = "EventsReceiverLink"; diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotReceivingLink.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotReceivingLink.cs index 6efe36b415..b16f846219 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotReceivingLink.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotReceivingLink.cs @@ -24,7 +24,7 @@ internal class AmqpIotReceivingLink private Action _onEventsReceived; private Action _onDeviceMessageReceived; private Action _onMethodReceived; - private Action _onTwinMessageReceived; + private Action _onTwinMessageReceived; public AmqpIotReceivingLink(ReceivingAmqpLink receivingAmqpLink) { @@ -259,7 +259,7 @@ private void DisposeDelivery(AmqpMessage amqpMessage, bool settled, Accepted acc #region Twin handling - internal void RegisterTwinListener(Action onDesiredPropertyReceived) + internal void RegisterTwinListener(Action onDesiredPropertyReceived) { _onTwinMessageReceived = onDesiredPropertyReceived; _receivingAmqpLink.RegisterMessageListener(OnTwinChangesReceived); @@ -278,9 +278,6 @@ private void OnTwinChangesReceived(AmqpMessage amqpMessage) string correlationId = amqpMessage.Properties?.CorrelationId?.ToString(); int status = GetStatus(amqpMessage); - Twin twin = null; - TwinCollection twinProperties = null; - if (status >= 400) { // Handle failures @@ -295,43 +292,12 @@ private void OnTwinChangesReceived(AmqpMessage amqpMessage) // Retry for Http status code request timeout, Too many requests and server errors var exception = new IotHubException(error, status >= 500 || status == 429 || status == 408); - _onTwinMessageReceived.Invoke(null, correlationId, null, exception); + _onTwinMessageReceived.Invoke(null, correlationId, exception); } } else { - if (correlationId == null) - { - // Here we are getting desired property update notifications and want to handle it first - using var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8); - string patch = reader.ReadToEnd(); - twinProperties = JsonConvert.DeserializeObject(patch); - } - else if (correlationId.StartsWith(AmqpTwinMessageType.Get.ToString(), StringComparison.OrdinalIgnoreCase)) - { - // This a response of a GET TWIN so return (set) the full twin - using var reader = new StreamReader(amqpMessage.BodyStream, System.Text.Encoding.UTF8); - string body = reader.ReadToEnd(); - TwinProperties properties = JsonConvert.DeserializeObject(body); - twin = new Twin(properties); - } - else if (correlationId.StartsWith(AmqpTwinMessageType.Patch.ToString(), StringComparison.OrdinalIgnoreCase)) - { - // This can be used to coorelate success response with updating reported properties - // However currently we do not have it as request response style implementation - Logging.Info("Updated twin reported properties successfully", nameof(OnTwinChangesReceived)); - } - else if (correlationId.StartsWith(AmqpTwinMessageType.Put.ToString(), StringComparison.OrdinalIgnoreCase)) - { - // This is an acknowledgement received from service for subscribing to desired property updates - Logging.Info("Subscribed for twin successfully", nameof(OnTwinChangesReceived)); - } - else - { - // This shouldn't happen - Logging.Info("Received a correlation Id for Twin operation that does not match Get, Patch or Put request", nameof(OnTwinChangesReceived)); - } - _onTwinMessageReceived.Invoke(twin, correlationId, twinProperties, null); + _onTwinMessageReceived.Invoke(amqpMessage, correlationId, null); } } finally diff --git a/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs b/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs index ae932f68d5..d94e7e5e84 100644 --- a/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs +++ b/iothub/device/src/Transport/AmqpIot/AmqpIotSendingLink.cs @@ -223,17 +223,14 @@ internal async Task SendTwinGetMessageAsync(string correlationId return new AmqpIotOutcome(outcome); } - internal async Task SendTwinPatchMessageAsync(string correlationId, TwinCollection reportedProperties, TimeSpan timeout) + internal async Task SendTwinPatchMessageAsync(string correlationId, Stream reportedProperties, TimeSpan timeout) { if (Logging.IsEnabled) { Logging.Enter(this, nameof(SendTwinPatchMessageAsync)); } - string body = JsonConvert.SerializeObject(reportedProperties); - var bodyStream = new MemoryStream(Encoding.UTF8.GetBytes(body)); - - using var amqpMessage = AmqpMessage.Create(bodyStream, true); + using var amqpMessage = AmqpMessage.Create(reportedProperties, true); amqpMessage.Properties.CorrelationId = correlationId; amqpMessage.MessageAnnotations.Map["operation"] = "PATCH"; amqpMessage.MessageAnnotations.Map["resource"] = "/properties/reported"; diff --git a/iothub/device/src/Transport/DefaultDelegatingHandler.cs b/iothub/device/src/Transport/DefaultDelegatingHandler.cs index ed9e4cd0cb..96e8b0c4fa 100644 --- a/iothub/device/src/Transport/DefaultDelegatingHandler.cs +++ b/iothub/device/src/Transport/DefaultDelegatingHandler.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Shared; @@ -178,18 +179,6 @@ public virtual Task DisableTwinPatchAsync(CancellationToken cancellationToken) return InnerHandler?.DisableTwinPatchAsync(cancellationToken) ?? TaskHelpers.CompletedTask; } - public virtual Task SendTwinGetAsync(CancellationToken cancellationToken) - { - ThrowIfDisposed(); - return InnerHandler?.SendTwinGetAsync(cancellationToken) ?? Task.FromResult((Twin)null); - } - - public virtual Task SendTwinPatchAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) - { - ThrowIfDisposed(); - return InnerHandler?.SendTwinPatchAsync(reportedProperties, cancellationToken) ?? TaskHelpers.CompletedTask; - } - public virtual Task EnableEventReceiveAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); @@ -202,16 +191,16 @@ public virtual Task DisableEventReceiveAsync(CancellationToken cancellationToken return InnerHandler?.DisableEventReceiveAsync(cancellationToken) ?? TaskHelpers.CompletedTask; } - public virtual Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public virtual Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); - return InnerHandler?.GetClientPropertiesAsync(payloadConvention, cancellationToken) ?? Task.FromResult(null); + return InnerHandler?.GetClientTwinPropertiesAsync(cancellationToken) ?? Task.FromResult(default); } - public virtual Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + public virtual Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken) { ThrowIfDisposed(); - return InnerHandler?.SendPropertyPatchAsync(reportedProperties, cancellationToken) ?? Task.FromResult(null); + return InnerHandler?.SendClientTwinPropertyPatchAsync(reportedProperties, cancellationToken) ?? Task.FromResult(null); } public virtual bool IsUsable => InnerHandler?.IsUsable ?? true; diff --git a/iothub/device/src/Transport/ErrorDelegatingHandler.cs b/iothub/device/src/Transport/ErrorDelegatingHandler.cs index d5e0be9c77..2b231e6209 100644 --- a/iothub/device/src/Transport/ErrorDelegatingHandler.cs +++ b/iothub/device/src/Transport/ErrorDelegatingHandler.cs @@ -105,16 +105,6 @@ public override Task DisableTwinPatchAsync(CancellationToken cancellationToken) return ExecuteWithErrorHandlingAsync(() => base.DisableTwinPatchAsync(cancellationToken)); } - public override Task SendTwinGetAsync(CancellationToken cancellationToken) - { - return ExecuteWithErrorHandlingAsync(() => base.SendTwinGetAsync(cancellationToken)); - } - - public override Task SendTwinPatchAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) - { - return ExecuteWithErrorHandlingAsync(() => base.SendTwinPatchAsync(reportedProperties, cancellationToken)); - } - public override Task AbandonAsync(string lockToken, CancellationToken cancellationToken) { return ExecuteWithErrorHandlingAsync(() => base.AbandonAsync(lockToken, cancellationToken)); @@ -145,14 +135,14 @@ public override Task SendMethodResponseAsync(MethodResponseInternal methodRespon return ExecuteWithErrorHandlingAsync(() => base.SendMethodResponseAsync(methodResponse, cancellationToken)); } - public override Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { - return ExecuteWithErrorHandlingAsync(() => base.GetClientPropertiesAsync(payloadConvention, cancellationToken)); + return ExecuteWithErrorHandlingAsync(() => base.GetClientTwinPropertiesAsync(cancellationToken)); } - public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + public override Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken) { - return ExecuteWithErrorHandlingAsync(() => base.SendPropertyPatchAsync(reportedProperties, cancellationToken)); + return ExecuteWithErrorHandlingAsync(() => base.SendClientTwinPropertyPatchAsync(reportedProperties, cancellationToken)); } private static bool IsNetworkExceptionChain(Exception exceptionChain) diff --git a/iothub/device/src/Transport/HttpTransportHandler.cs b/iothub/device/src/Transport/HttpTransportHandler.cs index c93d5e81b8..e798bd3af2 100644 --- a/iothub/device/src/Transport/HttpTransportHandler.cs +++ b/iothub/device/src/Transport/HttpTransportHandler.cs @@ -255,11 +255,6 @@ await _httpClientHelper.PostAsync( cancellationToken).ConfigureAwait(false); } - public override Task SendTwinGetAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException("Device twins are only supported with Mqtt protocol."); - } - public override async Task ReceiveAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -403,14 +398,14 @@ public override Task RejectAsync(string lockToken, CancellationToken cancellatio cancellationToken); } - public override Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { - throw new NotImplementedException("Property operations are not supported over HTTP. Please use MQTT protocol instead."); + throw new NotImplementedException("This operation is not supported over HTTP. Please use MQTT protocol instead."); } - public override Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + public override Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken) { - throw new NotImplementedException("Property operations are not supported over HTTP. Please use MQTT protocol instead."); + throw new NotImplementedException("This operation is not supported over HTTP. Please use MQTT protocol instead."); } // This is for invoking methods from an edge module to another edge device or edge module. diff --git a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs index b7c23cb247..d59846d416 100644 --- a/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs +++ b/iothub/device/src/Transport/Mqtt/MqttTransportHandler.cs @@ -912,36 +912,26 @@ public override async Task DisableTwinPatchAsync(CancellationToken cancellationT Logging.Exit(this, cancellationToken, nameof(DisableTwinPatchAsync)); } - public override async Task SendTwinGetAsync(CancellationToken cancellationToken) + public override async Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - EnsureValidState(); - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_014: `SendTwinGetAsync` shall allocate a `Message` object to hold the `GET` request using var request = new Message(); - - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_015: `SendTwinGetAsync` shall generate a GUID to use as the $rid property on the request - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_016: `SendTwinGetAsync` shall set the `Message` topic to '$iothub/twin/GET/?$rid=' where REQUEST_ID is the GUID that was generated string rid = Guid.NewGuid().ToString(); request.MqttTopicName = TwinGetTopic.FormatInvariant(rid); - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_017: `SendTwinGetAsync` shall wait for a response from the service with a matching $rid value - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_019: If the response is failed, `SendTwinGetAsync` shall return that failure to the caller. - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_020: If the response doesn't arrive within `MqttTransportHandler.TwinTimeout`, `SendTwinGetAsync` shall fail with a timeout error using Message response = await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_021: If the response contains a success code, `SendTwinGetAsync` shall return success to the caller - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_018: When a response is received, `SendTwinGetAsync` shall return the Twin object to the caller - using var reader = new StreamReader(response.GetBodyStream(), System.Text.Encoding.UTF8); + // We will use UTF-8 for decoding the service response. This is because UTF-8 is the only currently supported encoding format. + using var reader = new StreamReader(response.GetBodyStream(), DefaultPayloadConvention.Instance.PayloadEncoder.ContentEncoding); string body = reader.ReadToEnd(); try { - return new Twin - { - Properties = JsonConvert.DeserializeObject(body), - }; + // We will use NewtonSoft Json to deserialize the service response to the appropriate type; i.e. Twin for non-convention-based operation + // and ClientProperties for convention-based operations. + return JsonConvert.DeserializeObject(body); } catch (JsonReaderException ex) { @@ -952,69 +942,12 @@ public override async Task SendTwinGetAsync(CancellationToken cancellation } } - public override async Task SendTwinPatchAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - EnsureValidState(); - - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_025: `SendTwinPatchAsync` shall serialize the `reported` object into a JSON string - string body = JsonConvert.SerializeObject(reportedProperties); - using var bodyStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(body)); - - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_022: `SendTwinPatchAsync` shall allocate a `Message` object to hold the update request - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_026: `SendTwinPatchAsync` shall set the body of the message to the JSON string - using var request = new Message(bodyStream); - - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_023: `SendTwinPatchAsync` shall generate a GUID to use as the $rid property on the request - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_024: `SendTwinPatchAsync` shall set the `Message` topic to '$iothub/twin/PATCH/properties/reported/?$rid=' where REQUEST_ID is the GUID that was generated - string rid = Guid.NewGuid().ToString(); - request.MqttTopicName = TwinPatchTopic.FormatInvariant(rid); - - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_027: `SendTwinPatchAsync` shall wait for a response from the service with a matching $rid value - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_028: If the response is failed, `SendTwinPatchAsync` shall return that failure to the caller. - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_029: If the response doesn't arrive within `MqttTransportHandler.TwinTimeout`, `SendTwinPatchAsync` shall fail with a timeout error. - // Codes_SRS_CSHARP_MQTT_TRANSPORT_18_030: If the response contains a success code, `SendTwinPatchAsync` shall return success to the caller. - await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); - } - - public override async Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override async Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); EnsureValidState(); - using var request = new Message(); - string rid = Guid.NewGuid().ToString(); - request.MqttTopicName = TwinGetTopic.FormatInvariant(rid); - - using Message response = await SendTwinRequestAsync(request, rid, cancellationToken).ConfigureAwait(false); - - using var reader = new StreamReader(response.GetBodyStream(), payloadConvention.PayloadEncoder.ContentEncoding); - string body = reader.ReadToEnd(); - - try - { - ClientPropertiesAsDictionary clientPropertiesAsDictionary = JsonConvert.DeserializeObject(body); - var properties = clientPropertiesAsDictionary.ToClientProperties(payloadConvention); - return properties; - } - catch (JsonReaderException ex) - { - if (Logging.IsEnabled) - Logging.Error(this, $"Failed to parse Twin JSON: {ex}. Message body: '{body}'"); - - throw; - } - } - - public override async Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - EnsureValidState(); - - byte[] body = reportedProperties.GetPayloadObjectBytes(); - using var bodyStream = new MemoryStream(body); - - using var request = new Message(bodyStream); + using var request = new Message(reportedProperties); string rid = Guid.NewGuid().ToString(); request.MqttTopicName = TwinPatchTopic.FormatInvariant(rid); diff --git a/iothub/device/src/Transport/RetryDelegatingHandler.cs b/iothub/device/src/Transport/RetryDelegatingHandler.cs index de36ab6284..adaf158739 100644 --- a/iothub/device/src/Transport/RetryDelegatingHandler.cs +++ b/iothub/device/src/Transport/RetryDelegatingHandler.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -496,50 +497,6 @@ await _internalRetryPolicy } } - public override async Task SendTwinGetAsync(CancellationToken cancellationToken) - { - try - { - Logging.Enter(this, cancellationToken, nameof(SendTwinGetAsync)); - - return await _internalRetryPolicy - .ExecuteAsync( - async () => - { - await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); - return await base.SendTwinGetAsync(cancellationToken).ConfigureAwait(false); - }, - cancellationToken) - .ConfigureAwait(false); - } - finally - { - Logging.Exit(this, cancellationToken, nameof(SendTwinGetAsync)); - } - } - - public override async Task SendTwinPatchAsync(TwinCollection reportedProperties, CancellationToken cancellationToken) - { - try - { - Logging.Enter(this, reportedProperties, cancellationToken, nameof(SendTwinPatchAsync)); - - await _internalRetryPolicy - .ExecuteAsync( - async () => - { - await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); - await base.SendTwinPatchAsync(reportedProperties, cancellationToken).ConfigureAwait(false); - }, - cancellationToken) - .ConfigureAwait(false); - } - finally - { - Logging.Exit(this, reportedProperties, cancellationToken, nameof(SendTwinPatchAsync)); - } - } - public override async Task CompleteAsync(string lockToken, CancellationToken cancellationToken) { try @@ -606,47 +563,47 @@ await _internalRetryPolicy } } - public override async Task GetClientPropertiesAsync(PayloadConvention payloadConvention, CancellationToken cancellationToken) + public override async Task GetClientTwinPropertiesAsync(CancellationToken cancellationToken) { try { - Logging.Enter(this, payloadConvention, cancellationToken, nameof(SendPropertyPatchAsync)); + Logging.Enter(this, cancellationToken, nameof(GetClientTwinPropertiesAsync)); return await _internalRetryPolicy .ExecuteAsync( async () => { await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); - return await base.GetClientPropertiesAsync(payloadConvention, cancellationToken).ConfigureAwait(false); + return await base.GetClientTwinPropertiesAsync(cancellationToken).ConfigureAwait(false); }, cancellationToken) .ConfigureAwait(false); } finally { - Logging.Exit(this, payloadConvention, cancellationToken, nameof(SendPropertyPatchAsync)); + Logging.Exit(this, cancellationToken, nameof(GetClientTwinPropertiesAsync)); } } - public override async Task SendPropertyPatchAsync(ClientPropertyCollection reportedProperties, CancellationToken cancellationToken) + public override async Task SendClientTwinPropertyPatchAsync(Stream reportedProperties, CancellationToken cancellationToken) { try { - Logging.Enter(this, reportedProperties, cancellationToken, nameof(SendPropertyPatchAsync)); + Logging.Enter(this, reportedProperties, cancellationToken, nameof(SendClientTwinPropertyPatchAsync)); return await _internalRetryPolicy .ExecuteAsync( async () => { await EnsureOpenedAsync(cancellationToken).ConfigureAwait(false); - return await base.SendPropertyPatchAsync(reportedProperties, cancellationToken).ConfigureAwait(false); + return await base.SendClientTwinPropertyPatchAsync(reportedProperties, cancellationToken).ConfigureAwait(false); }, cancellationToken) .ConfigureAwait(false); } finally { - Logging.Exit(this, reportedProperties, cancellationToken, nameof(SendPropertyPatchAsync)); + Logging.Exit(this, reportedProperties, cancellationToken, nameof(SendClientTwinPropertyPatchAsync)); } } diff --git a/iothub/device/tests/DeviceClientTwinApiTests.cs b/iothub/device/tests/DeviceClientTwinApiTests.cs index f0e14363b6..3d8464ff01 100644 --- a/iothub/device/tests/DeviceClientTwinApiTests.cs +++ b/iothub/device/tests/DeviceClientTwinApiTests.cs @@ -1,11 +1,16 @@ // 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 Microsoft.VisualStudio.TestTools.UnitTesting; -using NSubstitute; -using System.Threading.Tasks; +using System.IO; +using System.Text; using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; using Microsoft.Azure.Devices.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using NSubstitute; namespace Microsoft.Azure.Devices.Client.Test { @@ -139,7 +144,7 @@ public async Task DeviceClientGetTwinAsyncCallsSendTwinGetAsync() // assert await innerHandler. Received(1). - SendTwinGetAsync(Arg.Any()).ConfigureAwait(false); + GetClientTwinPropertiesAsync(Arg.Any()).ConfigureAwait(false); } // Tests_SRS_DEVICECLIENT_18_002: `UpdateReportedPropertiesAsync` shall call `SendTwinPatchAsync` on the transport to update the reported properties @@ -151,14 +156,24 @@ public async Task DeviceClientUpdateReportedPropertiesAsyncCallsSendTwinPatchAsy var client = DeviceClient.CreateFromConnectionString(fakeConnectionString); client.InnerHandler = innerHandler; var props = new TwinCollection(); + string body = JsonConvert.SerializeObject(props); + + string receivedBody = null; + await innerHandler + .SendClientTwinPropertyPatchAsync( + Arg.Do(stream => + { + using var streamReader = new StreamReader(stream, Encoding.UTF8); + receivedBody = streamReader.ReadToEnd(); + }), + Arg.Any()) + .ConfigureAwait(false); // act await client.UpdateReportedPropertiesAsync(props).ConfigureAwait(false); // assert - await innerHandler. - Received(1). - SendTwinPatchAsync(Arg.Is(props), Arg.Any()).ConfigureAwait(false); + receivedBody.Should().Be(body); } // Tests_SRS_DEVICECLIENT_18_006: `UpdateReportedPropertiesAsync` shall throw an `ArgumentNull` exception if `reportedProperties` is null diff --git a/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs b/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs index 87275156ea..2fa015bf4e 100644 --- a/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs +++ b/iothub/device/tests/Mqtt/MqttTransportHandlerTests.cs @@ -91,13 +91,14 @@ public async Task MqttTransportHandlerEnableTwinPatchAsyncTokenCancellationReque [TestMethod] public async Task MqttTransportHandlerSendTwinGetAsyncTokenCancellationRequested() { - await TestOperationCanceledByToken(token => CreateFromConnectionString().SendTwinGetAsync(token)).ConfigureAwait(false); + await TestOperationCanceledByToken(token => CreateFromConnectionString().GetClientTwinPropertiesAsync(token)).ConfigureAwait(false); } [TestMethod] public async Task MqttTransportHandlerSendTwinPatchAsyncTokenCancellationRequested() { - await TestOperationCanceledByToken(token => CreateFromConnectionString().SendTwinPatchAsync(new TwinCollection(), token)).ConfigureAwait(false); + using var bodyStream = new MemoryStream(); + await TestOperationCanceledByToken(token => CreateFromConnectionString().SendClientTwinPropertyPatchAsync(bodyStream, token)).ConfigureAwait(false); } [TestMethod] @@ -383,7 +384,11 @@ public async Task MqttTransportHandlerSendTwinGetAsyncHappyPath() // act await transport.OpenAsync(CancellationToken.None).ConfigureAwait(false); - var twinReturned = await transport.SendTwinGetAsync(CancellationToken.None).ConfigureAwait(false); + var twinPropertiesReturned = await transport.GetClientTwinPropertiesAsync(CancellationToken.None).ConfigureAwait(false); + var twinReturned = new Twin + { + Properties = twinPropertiesReturned, + }; // assert Assert.AreEqual(twin.Properties.Desired["foo"].ToString(), twinReturned.Properties.Desired["foo"].ToString()); @@ -413,7 +418,7 @@ public async Task MqttTransportHandlerSendTwinGetAsyncReturnsFailure() // act & assert await transport.OpenAsync(CancellationToken.None).ConfigureAwait(false); - await transport.SendTwinGetAsync(CancellationToken.None).ExpectedAsync().ConfigureAwait(false); + await transport.GetClientTwinPropertiesAsync(CancellationToken.None).ExpectedAsync().ConfigureAwait(false); } // Tests_SRS_CSHARP_MQTT_TRANSPORT_18_020: If the response doesn't arrive within `MqttTransportHandler.TwinTimeout`, `SendTwinGetAsync` shall fail with a timeout error @@ -427,7 +432,7 @@ public async Task MqttTransportHandlerSendTwinGetAsyncTimesOut() // act & assert await transport.OpenAsync(CancellationToken.None).ConfigureAwait(false); - var twinReturned = await transport.SendTwinGetAsync(CancellationToken.None).ConfigureAwait(false); + var twinReturned = await transport.GetClientTwinPropertiesAsync(CancellationToken.None).ConfigureAwait(false); } // Tests_SRS_CSHARP_MQTT_TRANSPORT_18_022: `SendTwinPatchAsync` shall allocate a `Message` object to hold the update request @@ -451,7 +456,7 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncHappyPath() .Returns(msg => { var request = msg.Arg(); - StreamReader reader = new StreamReader(request.GetBodyStream(), System.Text.Encoding.UTF8); + using StreamReader reader = new StreamReader(request.GetBodyStream(), Encoding.UTF8); receivedBody = reader.ReadToEnd(); var response = new Message(); @@ -466,13 +471,14 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncHappyPath() return TaskHelpers.CompletedTask; }); + string expectedBody = JsonConvert.SerializeObject(props); + using var bodyStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedBody)); // act await transport.OpenAsync(CancellationToken.None).ConfigureAwait(false); - await transport.SendTwinPatchAsync(props, CancellationToken.None).ConfigureAwait(false); + await transport.SendClientTwinPropertyPatchAsync(bodyStream, CancellationToken.None).ConfigureAwait(false); // assert - string expectedBody = JsonConvert.SerializeObject(props); Assert.AreEqual(expectedBody, receivedBody); } @@ -499,10 +505,12 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncReturnsFailure() transport.OnMessageReceived(response); return TaskHelpers.CompletedTask; }); + string expectedBody = JsonConvert.SerializeObject(props); + using var bodyStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedBody)); // act & assert await transport.OpenAsync(CancellationToken.None).ConfigureAwait(false); - await transport.SendTwinPatchAsync(props, CancellationToken.None).ExpectedAsync().ConfigureAwait(false); + await transport.SendClientTwinPropertyPatchAsync(bodyStream, CancellationToken.None).ExpectedAsync().ConfigureAwait(false); } // Tests_SRS_CSHARP_MQTT_TRANSPORT_18_029: If the response doesn't arrive within `MqttTransportHandler.TwinTimeout`, `SendTwinPatchAsync` shall fail with a timeout error. @@ -514,10 +522,12 @@ public async Task MqttTransportHandlerSendTwinPatchAsyncTimesOut() var transport = this.CreateTransportHandlerWithMockChannel(out IChannel channel); transport.TwinTimeout = TimeSpan.FromMilliseconds(20); var props = new TwinCollection(); + string expectedBody = JsonConvert.SerializeObject(props); + using var bodyStream = new MemoryStream(Encoding.UTF8.GetBytes(expectedBody)); // act & assert await transport.OpenAsync(CancellationToken.None).ConfigureAwait(false); - await transport.SendTwinPatchAsync(props, CancellationToken.None).ConfigureAwait(false); + await transport.SendClientTwinPropertyPatchAsync(bodyStream, CancellationToken.None).ConfigureAwait(false); } // Tests_SRS_CSHARP_MQTT_TRANSPORT_28_04: If OnError is triggered after OpenAsync is called, WaitForTransportClosedAsync shall be invoked. From 90622b66d631b71dd86019f722d7865b78ca99ed Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 7 Oct 2021 15:21:44 -0700 Subject: [PATCH 76/77] fix(e2e-tests): Update E2E tests to initialize and dispose resources correctly --- e2e/test/helpers/TestDeviceCallbackHandler.cs | 2 +- e2e/test/iothub/properties/PropertiesE2ETests.cs | 2 +- e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/test/helpers/TestDeviceCallbackHandler.cs b/e2e/test/helpers/TestDeviceCallbackHandler.cs index 98d8351c68..02d2fbb24f 100644 --- a/e2e/test/helpers/TestDeviceCallbackHandler.cs +++ b/e2e/test/helpers/TestDeviceCallbackHandler.cs @@ -30,7 +30,7 @@ public class TestDeviceCallbackHandler : IDisposable private readonly SemaphoreSlim _clientPropertyCallbackSemaphore = new SemaphoreSlim(0, 1); private ExceptionDispatchInfo _clientPropertyExceptionDispatch; - private object _expectedClientPropertyValue = null; + private object _expectedClientPropertyValue; public TestDeviceCallbackHandler(DeviceClient deviceClient, TestDevice testDevice, MsTestLogger logger) { diff --git a/e2e/test/iothub/properties/PropertiesE2ETests.cs b/e2e/test/iothub/properties/PropertiesE2ETests.cs index a441a3adf6..6d68028541 100644 --- a/e2e/test/iothub/properties/PropertiesE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesE2ETests.cs @@ -275,7 +275,7 @@ private async Task Properties_ServiceSetsWritablePropertyAndDeviceReceivesEventA TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); - var writablePropertyCallbackSemaphore = new SemaphoreSlim(0, 1); + using var writablePropertyCallbackSemaphore = new SemaphoreSlim(0, 1); await deviceClient .SubscribeToWritablePropertyUpdateRequestsAsync( async (writableProperties, userContext) => diff --git a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs index 3f0869e4c6..31db30de03 100644 --- a/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs +++ b/e2e/test/iothub/properties/PropertiesWithComponentsE2ETests.cs @@ -284,7 +284,7 @@ private async Task PropertiesWithComponents_ServiceSetsWritablePropertyAndDevice TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, _devicePrefix).ConfigureAwait(false); using var deviceClient = DeviceClient.CreateFromConnectionString(testDevice.ConnectionString, transport); - var writablePropertyCallbackSemaphore = new SemaphoreSlim(0, 1); + using var writablePropertyCallbackSemaphore = new SemaphoreSlim(0, 1); await deviceClient .SubscribeToWritablePropertyUpdateRequestsAsync( async (writableProperties, userContext) => From 7d7bed7d7947aa3882ac6b958f4b6d313cc84b7e Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Thu, 7 Oct 2021 23:37:52 -0700 Subject: [PATCH 77/77] fix --- iothub/device/src/ClientTwinProperties.cs | 4 ++-- iothub/device/src/PayloadCollection.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/iothub/device/src/ClientTwinProperties.cs b/iothub/device/src/ClientTwinProperties.cs index 63b13195f5..53f1e1ea3c 100644 --- a/iothub/device/src/ClientTwinProperties.cs +++ b/iothub/device/src/ClientTwinProperties.cs @@ -23,8 +23,8 @@ internal ClientTwinProperties() internal ClientProperties ToClientProperties(PayloadConvention payloadConvention) { - ClientPropertyCollection writablePropertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Desired, payloadConvention); - ClientPropertyCollection propertyCollection = ClientPropertyCollection.FromClientTwinDictionary(Reported, payloadConvention); + ClientPropertyCollection writablePropertyCollection = ClientPropertyCollection.FromClientPropertiesAsDictionary(Desired, payloadConvention); + ClientPropertyCollection propertyCollection = ClientPropertyCollection.FromClientPropertiesAsDictionary(Reported, payloadConvention); return new ClientProperties(writablePropertyCollection, propertyCollection); } diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index 5155d45b69..d4dcbc2295 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -124,6 +124,12 @@ public bool TryGetValue(string key, out T value) if (string.IsNullOrWhiteSpace(key)) { value = default; + return false; + } + + // While retrieving the telemetry value from the collection, a simple dictionary indexer should work. + // While retrieving the property value from the collection: + // 1. A property collection constructed by the client application - can be retrieved using dictionary indexer. // 2. Client property received through writable property update callbacks - stored internally as a WritableClientProperty. // 3. Client property returned through GetClientProperties: // a. Client reported properties sent by the client application in response to writable property update requests - stored as a JSON object