Skip to content

Commit

Permalink
Bring PnP feature to preview (#2005)
Browse files Browse the repository at this point in the history
* feat(shared): Add common resources for convention-based operations

* feat(iot-device): Add support for convention-based telemetry operation

* feat(iot-device): Add support for convention-based command operations

* * feat(iot-device): Add support for convention-based properties operations

* feat(iot-device): Add support for convention-based properties operations

* feat(e2e-tests): Add telemetry E2E tests

* feat(e2e-tests): Add command E2E tests

* fix(iot-device): Updating client property collection to handle no convention

* feat(samples): Add thermostat and temperature controller sample

* fix(doc, samples): Update API design doc and move SystemTextJson helper to samples

* fix(iot-device): Separate out root-level and component-level property addition operations

* feat(tests): Add unit tests for ClientPropertyCollection

feat(tests): Add unit tests for ClientPropertyCollection

Co-authored-by: Abhipsa Misra <[email protected]>

* feat(e2e-tests): Add properties E2E tests

Co-authored-by: Abhipsa Misra <[email protected]>

* feat(e2e-tests): Add fault injection tests for properties operations (#2001)

* fix(iot-device, shared, samples): Rename StatusCodes to CommonClientResponseCodes and add a comment to highlight ClientOptions behavior

Co-authored-by: James Davis ⛺️🏔 <[email protected]>
Co-authored-by: jamdavi <[email protected]>
  • Loading branch information
3 people authored Jun 7, 2021
1 parent 92f49be commit 3c14656
Show file tree
Hide file tree
Showing 79 changed files with 6,739 additions and 522 deletions.
20 changes: 20 additions & 0 deletions azureiot.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
201 changes: 130 additions & 71 deletions e2e/test/Helpers/TestDeviceCallbackHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +28,10 @@ public class TestDeviceCallbackHandler : IDisposable
private ExceptionDispatchInfo _receiveMessageExceptionDispatch;
private Message _expectedMessageSentByService;

private readonly SemaphoreSlim _clientPropertyCallbackSemaphore = new SemaphoreSlim(0, 1);
private ExceptionDispatchInfo _clientPropertyExceptionDispatch;
private object _expectedClientPropertyValue;

public TestDeviceCallbackHandler(DeviceClient deviceClient, TestDevice testDevice, MsTestLogger logger)
{
_deviceClient = deviceClient;
Expand All @@ -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)
Expand All @@ -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<bool>(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<bool>(true);
},
userContext)
.ConfigureAwait(false);
}

public async Task WaitForTwinCallbackAsync(CancellationToken ct)
Expand All @@ -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)
Expand All @@ -158,6 +175,48 @@ public async Task WaitForReceiveMessageCallbackAsync(CancellationToken ct)
_receiveMessageExceptionDispatch?.Throw();
}

public async Task SetClientPropertyUpdateCallbackHandlerAsync<T>(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();
Expand Down
Loading

0 comments on commit 3c14656

Please sign in to comment.