Skip to content

Commit 397c66a

Browse files
author
David R. Williamson
authored
More orphaned devices cleanup, json fixes (#3147)
* More orphaned devices cleanup * Exception handling * PR feedback * Fix yaml to not upload cc when skipped * Add more enum json names back * Test changes * Add helper to IotHubScheduledJobResponse * Fix test cleanup * Log test failure * Fix some logging * Misc
1 parent 20e574b commit 397c66a

File tree

51 files changed

+507
-486
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+507
-486
lines changed

e2e/test/helpers/TestDevice.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ internal sealed class TestDevice : IAsyncDisposable
3434
});
3535

3636
private X509Certificate2 _authCertificate;
37+
38+
/// <summary>
39+
/// A single service client instance to be used by all the tests that simply need one initialized with a connection string.
40+
/// </summary>
41+
/// <remarks>
42+
/// It is better for tests to use a single instance so we don't run out of sockets due to the HttpClient dispose anti-pattern.
43+
/// <para>
44+
/// We won't dispose this client either, but it'll be fine because the test process will exit and memory leaks aren't a concern then.
45+
/// </para>
46+
/// </remarks>
3747
internal static IotHubServiceClient ServiceClient { get; } = new(TestConfiguration.IotHub.ConnectionString);
3848

3949
private TestDevice(Device device, IAuthenticationMethod authenticationMethod)
@@ -79,7 +89,7 @@ internal string ConnectionString
7989
/// <summary>
8090
/// Device identity object.
8191
/// </summary>
82-
internal Device Device { get; private set; }
92+
internal Device Device { get; set; }
8393

8494
internal IotHubDeviceClient DeviceClient { get; private set; }
8595

@@ -112,32 +122,32 @@ internal IotHubDeviceClient CreateDeviceClient(IotHubClientOptions options = def
112122
/// Sometimes the just newly created device can fail to connect if there is lag in the hub for the identity existing.
113123
/// Retry a few times to prevent test failures.
114124
/// </summary>
115-
internal async Task OpenWithRetryAsync()
125+
internal async Task OpenWithRetryAsync(CancellationToken ct = default)
116126
{
117127
if (DeviceClient == null)
118128
{
119129
throw new InvalidOperationException("The device client has not been initialized. Call CreateDeviceClient() first.");
120130
}
121-
await OpenWithRetryAsync(DeviceClient).ConfigureAwait(false);
131+
await OpenWithRetryAsync(DeviceClient, ct).ConfigureAwait(false);
122132
}
123133

124134
/// <summary>
125-
internal static async Task OpenWithRetryAsync(IotHubDeviceClient client)
135+
internal static async Task OpenWithRetryAsync(IotHubDeviceClient client, CancellationToken ct = default)
126136
{
127137
int attempt = 1;
128138
while (true)
129139
{
130140
try
131141
{
132142
VerboseTestLogger.WriteLine("Opening device client...");
133-
await client.OpenAsync().ConfigureAwait(false);
143+
await client.OpenAsync(ct).ConfigureAwait(false);
134144
break;
135145
}
136146
catch (IotHubClientException ex) when (ex.ErrorCode == IotHubClientErrorCode.Unauthorized
137147
&& attempt++ < 4)
138148
{
139149
VerboseTestLogger.WriteLine($"Attempt #{attempt} failed to open device connection due to {ex}");
140-
await Task.Delay(1000).ConfigureAwait(false);
150+
await Task.Delay(1000, ct).ConfigureAwait(false);
141151
}
142152
}
143153
}

e2e/test/helpers/TestModule.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,57 @@ namespace Microsoft.Azure.Devices.E2ETests.Helpers
88
{
99
internal class TestModule : IAsyncDisposable
1010
{
11-
private Module _module;
12-
private TestDevice _testDevice;
13-
1411
/// <summary>
1512
/// Used in conjunction with ModuleClient.CreateFromConnectionString()
1613
/// </summary>
17-
public string ConnectionString =>
18-
$"HostName={TestConfiguration.IotHub.GetIotHubHostName()};DeviceId={_module.DeviceId};ModuleId={_module.Id};SharedAccessKey={_module.Authentication.SymmetricKey.PrimaryKey}";
14+
internal string ConnectionString =>
15+
$"HostName={TestConfiguration.IotHub.GetIotHubHostName()};DeviceId={Module.DeviceId};ModuleId={Module.Id};SharedAccessKey={Module.Authentication.SymmetricKey.PrimaryKey}";
1916

2017
/// <summary>
2118
/// Module Id
2219
/// </summary>
23-
public string Id => _module.Id;
20+
internal string Id => Module.Id;
2421

2522
/// <summary>
2623
/// Device Id
2724
/// </summary>
28-
public string DeviceId => _testDevice.Id;
25+
internal string DeviceId => TestDevice.Id;
26+
27+
/// <summary>
28+
/// The device that hosts the module.
29+
/// </summary>
30+
internal TestDevice TestDevice { get; set; }
31+
32+
/// <summary>
33+
/// The created module.
34+
/// </summary>
35+
internal Module Module { get; set; }
2936

3037
/// <summary>
3138
/// Factory method.
3239
/// </summary>
33-
public static async Task<TestModule> GetTestModuleAsync(string deviceNamePrefix, string moduleNamePrefix)
40+
internal static async Task<TestModule> GetTestModuleAsync(string deviceNamePrefix, string moduleNamePrefix)
3441
{
3542
var testModule = new TestModule
3643
{
37-
_testDevice = await TestDevice.GetTestDeviceAsync(deviceNamePrefix).ConfigureAwait(false)
44+
TestDevice = await TestDevice.GetTestDeviceAsync(deviceNamePrefix).ConfigureAwait(false)
3845
};
3946

40-
string deviceName = testModule._testDevice.Id;
47+
string deviceName = testModule.TestDevice.Id;
4148
string moduleName = $"E2E_{moduleNamePrefix}_{Guid.NewGuid()}";
4249

4350
IotHubServiceClient sc = TestDevice.ServiceClient;
44-
testModule._module = await sc.Modules.CreateAsync(new Module(deviceName, moduleName)).ConfigureAwait(false);
51+
testModule.Module = await sc.Modules.CreateAsync(new Module(deviceName, moduleName)).ConfigureAwait(false);
4552
VerboseTestLogger.WriteLine($"{nameof(GetTestModuleAsync)}: Using device {testModule.DeviceId} with module {testModule.Id}.");
4653

4754
return testModule;
4855
}
4956

5057
public async ValueTask DisposeAsync()
5158
{
52-
await _testDevice.DisposeAsync().ConfigureAwait(false);
59+
// This will delete the device and the module along with it.
60+
await TestDevice.DisposeAsync().ConfigureAwait(false);
61+
5362
GC.SuppressFinalize(this);
5463
}
5564
}

e2e/test/helpers/logging/VerboseTestLogger.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System;
5-
using System.Diagnostics;
65

76
namespace Microsoft.Azure.Devices.E2ETests
87
{
98
public static class VerboseTestLogger
109
{
1110
public static void WriteLine(string message)
1211
{
13-
EventSourceTestLogger.Log.TestVerboseMessage(message);
14-
Debug.WriteLine(message);
1512
Console.WriteLine(message);
13+
EventSourceTestLogger.Log.TestVerboseMessage(message);
1614
}
1715
}
1816
}

e2e/test/helpers/templates/PoolingOverAmqp.cs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ internal static async Task TestPoolAmqpAsync(
2121
IotHubClientAmqpSettings transportSettings,
2222
int poolSize,
2323
int devicesCount,
24-
Func<IotHubDeviceClient, TestDevice, TestDeviceCallbackHandler, Task> initOperation,
25-
Func<IotHubDeviceClient, TestDevice, TestDeviceCallbackHandler, Task> testOperation,
24+
Func<TestDevice, TestDeviceCallbackHandler, Task> initOperation,
25+
Func<TestDevice, TestDeviceCallbackHandler, Task> testOperation,
2626
Func<Task> cleanupOperation,
2727
ConnectionStringAuthScope authScope,
2828
bool ignoreConnectionStatus)
@@ -34,43 +34,41 @@ internal static async Task TestPoolAmqpAsync(
3434
};
3535

3636
var testDevices = new List<TestDevice>(devicesCount);
37-
var deviceClients = new List<IotHubDeviceClient>(devicesCount);
3837
var testDeviceCallbackHandlers = new List<TestDeviceCallbackHandler>(devicesCount);
3938
var amqpConnectionStatuses = new List<AmqpConnectionStatusChange>(devicesCount);
4039
var operations = new List<Task>(devicesCount);
4140

42-
// Arrange
43-
// Initialize the test device client instances
44-
// Set the device client connection status change handler
45-
VerboseTestLogger.WriteLine($"{nameof(PoolingOverAmqp)} Initializing device clients for multiplexing test");
46-
for (int i = 0; i < devicesCount; i++)
41+
try
4742
{
43+
// Arrange
4844
// Initialize the test device client instances
49-
TestDevice testDevice = await TestDevice.GetTestDeviceAsync($"{devicePrefix}_{i}_").ConfigureAwait(false);
50-
IotHubDeviceClient deviceClient = testDevice.CreateDeviceClient(new IotHubClientOptions(transportSettings), authScope: authScope);
51-
5245
// Set the device client connection status change handler
53-
var amqpConnectionStatusChange = new AmqpConnectionStatusChange();
54-
deviceClient.ConnectionStatusChangeCallback = amqpConnectionStatusChange.ConnectionStatusChangeHandler;
46+
VerboseTestLogger.WriteLine($"{nameof(PoolingOverAmqp)} Initializing device clients for multiplexing test");
47+
for (int deviceCreateIndex = 0; deviceCreateIndex < devicesCount; deviceCreateIndex++)
48+
{
49+
// Initialize the test device client instances
50+
TestDevice testDevice = await TestDevice.GetTestDeviceAsync($"{devicePrefix}_{deviceCreateIndex}_").ConfigureAwait(false);
51+
IotHubDeviceClient deviceClient = testDevice.CreateDeviceClient(new IotHubClientOptions(transportSettings), authScope: authScope);
5552

56-
var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice);
53+
// Set the device client connection status change handler
54+
var amqpConnectionStatusChange = new AmqpConnectionStatusChange();
55+
deviceClient.ConnectionStatusChangeCallback = amqpConnectionStatusChange.ConnectionStatusChangeHandler;
5756

58-
testDevices.Add(testDevice);
59-
deviceClients.Add(deviceClient);
60-
testDeviceCallbackHandlers.Add(testDeviceCallbackHandler);
61-
amqpConnectionStatuses.Add(amqpConnectionStatusChange);
57+
var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice);
6258

63-
if (initOperation != null)
64-
{
65-
await initOperation(deviceClient, testDevice, testDeviceCallbackHandler).ConfigureAwait(false);
59+
testDevices.Add(testDevice);
60+
testDeviceCallbackHandlers.Add(testDeviceCallbackHandler);
61+
amqpConnectionStatuses.Add(amqpConnectionStatusChange);
62+
63+
if (initOperation != null)
64+
{
65+
await initOperation(testDevice, testDeviceCallbackHandler).ConfigureAwait(false);
66+
}
6667
}
67-
}
6868

69-
try
70-
{
71-
for (int i = 0; i < devicesCount; i++)
69+
for (int deviceInitIndex = 0; deviceInitIndex < devicesCount; deviceInitIndex++)
7270
{
73-
operations.Add(testOperation(deviceClients[i], testDevices[i], testDeviceCallbackHandlers[i]));
71+
operations.Add(testOperation(testDevices[deviceInitIndex], testDeviceCallbackHandlers[deviceInitIndex]));
7472
}
7573
await Task.WhenAll(operations).ConfigureAwait(false);
7674
operations.Clear();
@@ -80,10 +78,17 @@ internal static async Task TestPoolAmqpAsync(
8078
// Close the service-side components and dispose the device client instances.
8179
if (cleanupOperation != null)
8280
{
83-
await cleanupOperation().ConfigureAwait(false);
81+
try
82+
{
83+
await cleanupOperation().ConfigureAwait(false);
84+
}
85+
catch (Exception ex)
86+
{
87+
VerboseTestLogger.WriteLine($"Failed to run clean up due to {ex}");
88+
}
8489
}
8590

86-
testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose());
91+
testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => { try { testDeviceCallbackHandler.Dispose(); } catch { } });
8792
await Task.WhenAll(testDevices.Select(x => x.DisposeAsync().AsTask())).ConfigureAwait(false);
8893
}
8994
}

e2e/test/iothub/device/CombinedClientOperationsPoolAmqpTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private async Task DeviceCombinedClientOperationsAsync(
5959
// Twin properties
6060
var twinPropertyMap = new Dictionary<string, List<string>>();
6161

62-
async Task InitOperationAsync(IotHubDeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _)
62+
async Task InitOperationAsync(TestDevice testDevice, TestDeviceCallbackHandler _)
6363
{
6464
IList<Task> initOperations = new List<Task>();
6565

@@ -73,29 +73,29 @@ async Task InitOperationAsync(IotHubDeviceClient deviceClient, TestDevice testDe
7373

7474
// Set method handler
7575
VerboseTestLogger.WriteLine($"{nameof(CombinedClientOperationsPoolAmqpTests)}: Set direct method {MethodName} for device={testDevice.Id}");
76-
Task<Task> methodReceivedTask = MethodE2ETests.SetDeviceReceiveMethodAsync(deviceClient, MethodName);
76+
Task<Task> methodReceivedTask = MethodE2ETests.SetDeviceReceiveMethodAsync(testDevice.DeviceClient, MethodName);
7777
initOperations.Add(methodReceivedTask);
7878

7979
// Set the twin desired properties callback
8080
VerboseTestLogger.WriteLine($"{nameof(CombinedClientOperationsPoolAmqpTests)}: Set desired property callback for device={testDevice.Id}");
8181
string propName = Guid.NewGuid().ToString();
8282
string propValue = Guid.NewGuid().ToString();
8383
twinPropertyMap.Add(testDevice.Id, new List<string> { propName, propValue });
84-
Task<Task> updateReceivedTask = TwinE2ETests.SetTwinPropertyUpdateCallbackHandlerAsync(deviceClient, propName, propValue);
84+
Task<Task> updateReceivedTask = TwinE2ETests.SetTwinPropertyUpdateCallbackHandlerAsync(testDevice.DeviceClient, propName, propValue);
8585
initOperations.Add(updateReceivedTask);
8686

8787
await Task.WhenAll(initOperations).ConfigureAwait(false);
8888
}
8989

90-
async Task TestOperationAsync(IotHubDeviceClient deviceClient, TestDevice testDevice, TestDeviceCallbackHandler _)
90+
async Task TestOperationAsync(TestDevice testDevice, TestDeviceCallbackHandler _)
9191
{
9292
IList<Task> clientOperations = new List<Task>();
93-
await deviceClient.OpenAsync().ConfigureAwait(false);
93+
await testDevice.OpenWithRetryAsync().ConfigureAwait(false);
9494

9595
// D2C Operation
9696
VerboseTestLogger.WriteLine($"{nameof(CombinedClientOperationsPoolAmqpTests)}: Operation 1: Send D2C for device={testDevice.Id}");
9797
TelemetryMessage message = TelemetryE2ETests.ComposeD2cTestMessage(out string _, out string _);
98-
Task sendD2cMessage = deviceClient.SendTelemetryAsync(message);
98+
Task sendD2cMessage = testDevice.DeviceClient.SendTelemetryAsync(message);
9999
clientOperations.Add(sendD2cMessage);
100100

101101
// C2D Operation
@@ -104,7 +104,7 @@ async Task TestOperationAsync(IotHubDeviceClient deviceClient, TestDevice testDe
104104
Message msg = msgSent.Item1;
105105
string payload = msgSent.Item2;
106106

107-
Task verifyDeviceClientReceivesMessage = MessageReceiveE2ETests.VerifyReceivedC2dMessageAsync(deviceClient, testDevice.Id, msg, payload);
107+
Task verifyDeviceClientReceivesMessage = MessageReceiveE2ETests.VerifyReceivedC2dMessageAsync(testDevice.DeviceClient, testDevice.Id, msg, payload);
108108
clientOperations.Add(verifyDeviceClientReceivesMessage);
109109

110110
// Invoke direct methods
@@ -114,7 +114,7 @@ async Task TestOperationAsync(IotHubDeviceClient deviceClient, TestDevice testDe
114114

115115
// Set reported twin properties
116116
VerboseTestLogger.WriteLine($"{nameof(CombinedClientOperationsPoolAmqpTests)}: Operation 4: Set reported property for device={testDevice.Id}");
117-
Task setReportedProperties = TwinE2ETests.Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(deviceClient, testDevice.Id, Guid.NewGuid().ToString());
117+
Task setReportedProperties = TwinE2ETests.Twin_DeviceSetsReportedPropertyAndGetsItBackAsync(testDevice.DeviceClient, testDevice.Id, Guid.NewGuid().ToString());
118118
clientOperations.Add(setReportedProperties);
119119

120120
// Receive set desired twin properties

e2e/test/iothub/device/FileUploadE2ETests.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,24 @@ private async Task UploadFileGranularAsync(Stream source, string filename, IotHu
142142
{
143143
var notification = new FileUploadCompletionNotification("invalid-correlation-id", uploadTask.IsCompleted);
144144

145-
// act
146-
Func<Task> act = async () => await deviceClient.CompleteFileUploadAsync(notification).ConfigureAwait(false);
147-
148-
// assert
149-
var error = await act.Should().ThrowAsync<IotHubClientException>();
150-
error.And.ErrorCode.Should().Be(IotHubClientErrorCode.ServerError);
151-
error.And.IsTransient.Should().BeTrue();
145+
// act and assert
146+
147+
try
148+
{
149+
// TODO: the HTTP layer handles errors differently and does not produce the right kind of exceptions.
150+
// It should be updated to throw the same kind of exceptions as MQTT and AMQP.
151+
await deviceClient.CompleteFileUploadAsync(notification).ConfigureAwait(false);
152+
}
153+
catch (IotHubClientException ex)
154+
{
155+
// Gateway V1 flow
156+
ex.ErrorCode.Should().Be(IotHubClientErrorCode.ServerError);
157+
}
158+
catch (ArgumentException ex)
159+
{
160+
// Gateway V2 flow
161+
ex.Message.Should().Contain("400006");
162+
}
152163
}
153164
}
154165
}

0 commit comments

Comments
 (0)