forked from Azure/iotedge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix: CloudConnection did not forward close() call to cloud proxy (Azu…
…re#4546) The main change is in BrokeredCloudConnection.cs where it did not forward the call to CloudProxy. The rest of the change is only about to being able to log the Identity in case of failure. The removed "TODO" about the subscription is not needed anymore as the proper place was in ConnectionHander.cs/RemoveConnectionAsync() to remove existing subscriptions.
- Loading branch information
Showing
6 changed files
with
286 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
157 changes: 157 additions & 0 deletions
157
...st/Microsoft.Azure.Devices.Edge.Hub.MqttBrokerAdapter.Test/BrokeredCloudConnectionTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
namespace Microsoft.Azure.Devices.Edge.Hub.MqttBrokerAdapter.Test | ||
{ | ||
using System; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Azure.Devices.Edge.Hub.Core; | ||
using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; | ||
using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; | ||
using Microsoft.Azure.Devices.Edge.Util; | ||
using Moq; | ||
using Xunit; | ||
|
||
public class BrokeredCloudConnectionTest | ||
{ | ||
const string IotHubHostName = "somehub.azure-devices.net"; | ||
const string ConnectivityTopic = "$internal/connectivity"; | ||
|
||
[Fact] | ||
public async Task AddRemoveDeviceConnection() | ||
{ | ||
var deviceId = "some_device"; | ||
var deviceCredentials = new TokenCredentials(new DeviceIdentity(IotHubHostName, deviceId), "some_token", "some_product", Option.None<string>(), Option.None<string>(), false); | ||
|
||
var (connectionManager, cloudProxyDispatcher) = SetupEnvironment(); | ||
|
||
await SignalConnected(cloudProxyDispatcher); | ||
|
||
Try<ICloudProxy> cloudProxyTry = await connectionManager.CreateCloudConnectionAsync(deviceCredentials); | ||
Assert.True(cloudProxyTry.Success); | ||
Assert.True(cloudProxyTry.Value.IsActive); | ||
|
||
await connectionManager.RemoveDeviceConnection(deviceId); | ||
Assert.False(cloudProxyTry.Value.IsActive); | ||
} | ||
|
||
[Fact] | ||
public async Task DisconnectRemovesConnections() | ||
{ | ||
var deviceId = "some_device"; | ||
var deviceCredentials = new TokenCredentials(new DeviceIdentity(IotHubHostName, deviceId), "some_token", "some_product", Option.None<string>(), Option.None<string>(), false); | ||
|
||
var (connectionManager, cloudProxyDispatcher) = SetupEnvironment(); | ||
|
||
await SignalConnected(cloudProxyDispatcher); | ||
|
||
Try<ICloudProxy> cloudProxyTry = await connectionManager.CreateCloudConnectionAsync(deviceCredentials); | ||
Assert.True(cloudProxyTry.Success); | ||
Assert.True(cloudProxyTry.Value.IsActive); | ||
|
||
await SignalDisconnected(cloudProxyDispatcher); | ||
|
||
// After the signal triggered, it is async disconnecting cloud proxies, so give some time | ||
await WaitUntil(() => !cloudProxyTry.Value.IsActive, TimeSpan.FromSeconds(5)); | ||
|
||
Assert.False(cloudProxyTry.Value.IsActive); | ||
} | ||
|
||
[Fact] | ||
public async Task ConnectionEventHandlersMaintainedProperly() | ||
{ | ||
// This test is motivated by an actual bug when new cloud proxies were added to | ||
// connection (C#) events but then they never got removed, causing memory leak and | ||
// triggering event handlers multiple times, as the old proxies kept hanging on | ||
// the event. | ||
|
||
var deviceId = "some_device"; | ||
var deviceCredentials = new TokenCredentials(new DeviceIdentity(IotHubHostName, deviceId), "some_token", "some_product", Option.None<string>(), Option.None<string>(), false); | ||
|
||
var (connectionManager, cloudProxyDispatcher) = SetupEnvironment(); | ||
|
||
await SignalConnected(cloudProxyDispatcher); | ||
|
||
Try<ICloudProxy> cloudProxyTry = await connectionManager.CreateCloudConnectionAsync(deviceCredentials); | ||
Assert.True(cloudProxyTry.Success); | ||
Assert.True(cloudProxyTry.Value.IsActive); | ||
|
||
// Disconnect/Connect a few times, this generates new and new proxies. | ||
var lastProxy = cloudProxyTry.Value.AsPrivateAccessible().InnerCloudProxy as BrokeredCloudProxy; | ||
for (var i = 0; i < 3; i++) | ||
{ | ||
await SignalDisconnected(cloudProxyDispatcher); | ||
|
||
await WaitUntil(() => !lastProxy.IsActive, TimeSpan.FromSeconds(5)); | ||
|
||
// As the proxy went inactive, the subscription list must be empty | ||
var subscribers = cloudProxyDispatcher.AsPrivateAccessible().ConnectionStatusChangedEvent as MulticastDelegate; | ||
Assert.NotNull(subscribers); | ||
|
||
var subscriberList = subscribers.GetInvocationList().Select(l => l.Target).OfType<BrokeredCloudProxy>().ToArray(); | ||
Assert.Empty(subscriberList); | ||
|
||
// Bring up a new proxy | ||
await SignalConnected(cloudProxyDispatcher); | ||
|
||
var currentProxyTry = (await connectionManager.GetCloudConnection(deviceId)).GetOrElse((ICloudProxy)null); | ||
Assert.NotNull(currentProxyTry); | ||
Assert.True(currentProxyTry.IsActive); | ||
|
||
var currentProxy = currentProxyTry.AsPrivateAccessible().InnerCloudProxy as BrokeredCloudProxy; | ||
|
||
// Just to be sure that this is a new proxy (and not the old is re-activated) | ||
Assert.NotEqual(lastProxy, currentProxy); | ||
|
||
// The new proxy must have subscribed and it should be the only subscriber | ||
subscribers = cloudProxyDispatcher.AsPrivateAccessible().ConnectionStatusChangedEvent as MulticastDelegate; | ||
Assert.NotNull(subscribers); | ||
|
||
subscriberList = subscribers.GetInvocationList().Select(l => l.Target).OfType<BrokeredCloudProxy>().ToArray(); | ||
Assert.Single(subscriberList); | ||
Assert.Equal(currentProxy, subscriberList.First()); | ||
|
||
lastProxy = currentProxy; | ||
} | ||
} | ||
|
||
Task SignalConnected(BrokeredCloudProxyDispatcher brokeredCloudProxyDispatcher) => SignalConnectionEvent(brokeredCloudProxyDispatcher, "Connected"); | ||
Task SignalDisconnected(BrokeredCloudProxyDispatcher brokeredCloudProxyDispatcher) => SignalConnectionEvent(brokeredCloudProxyDispatcher, "Disconnected"); | ||
|
||
async Task SignalConnectionEvent(BrokeredCloudProxyDispatcher brokeredCloudProxyDispatcher, string status) | ||
{ | ||
var packet = new MqttPublishInfo(ConnectivityTopic, Encoding.UTF8.GetBytes("{\"status\":\"" + status + "\"}")); | ||
await brokeredCloudProxyDispatcher.HandleAsync(packet); | ||
} | ||
|
||
(ConnectionManager, BrokeredCloudProxyDispatcher) SetupEnvironment() | ||
{ | ||
var cloudProxyDispatcher = new BrokeredCloudProxyDispatcher(); | ||
cloudProxyDispatcher.SetConnector(Mock.Of<IMqttBrokerConnector>()); | ||
|
||
var cloudConnectionProvider = new BrokeredCloudConnectionProvider(cloudProxyDispatcher); | ||
cloudConnectionProvider.BindEdgeHub(Mock.Of<IEdgeHub>()); | ||
|
||
var deviceConnectivityManager = new BrokeredDeviceConnectivityManager(cloudProxyDispatcher); | ||
|
||
var connectionManager = new ConnectionManager( | ||
cloudConnectionProvider, | ||
Mock.Of<ICredentialsCache>(), | ||
new IdentityProvider(IotHubHostName), | ||
deviceConnectivityManager); | ||
|
||
return (connectionManager, cloudProxyDispatcher); | ||
} | ||
|
||
async Task WaitUntil(Func<bool> condition, TimeSpan timeout) | ||
{ | ||
var startTime = DateTime.Now; | ||
|
||
while (!condition() && DateTime.Now - startTime < timeout) | ||
{ | ||
await Task.Delay(100); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.