Skip to content

Commit

Permalink
EdgeHub: Make Device/Module Client operation timeout configurable (#542)
Browse files Browse the repository at this point in the history
* Make operation timeout configurable

* Fix setting client value

* Fix tests
  • Loading branch information
varunpuranik authored and myagley committed Nov 26, 2018
1 parent 5081408 commit 6102e31
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class ClientTokenCloudConnection : CloudConnection, IClientTokenCloudConnection
IClientProvider clientProvider,
ICloudListener cloudListener,
TimeSpan idleTimeout,
bool closeOnIdleTimeout)
bool closeOnIdleTimeout,
TimeSpan operationTimeout)
: base(
identity,
connectionStatusChangedHandler,
Expand All @@ -44,7 +45,8 @@ class ClientTokenCloudConnection : CloudConnection, IClientTokenCloudConnection
clientProvider,
cloudListener,
idleTimeout,
closeOnIdleTimeout)
closeOnIdleTimeout,
operationTimeout)
{
}

Expand All @@ -56,7 +58,8 @@ public static async Task<ClientTokenCloudConnection> Create(
IClientProvider clientProvider,
ICloudListener cloudListener,
TimeSpan idleTimeout,
bool closeOnIdleTimeout)
bool closeOnIdleTimeout,
TimeSpan operationTimeout)
{
Preconditions.CheckNotNull(tokenCredentials, nameof(tokenCredentials));
var cloudConnection = new ClientTokenCloudConnection(
Expand All @@ -67,7 +70,8 @@ public static async Task<ClientTokenCloudConnection> Create(
clientProvider,
cloudListener,
idleTimeout,
closeOnIdleTimeout);
closeOnIdleTimeout,
operationTimeout);
ITokenProvider tokenProvider = new ClientTokenBasedTokenProvider(tokenCredentials, cloudConnection);
ICloudProxy cloudProxy = await cloudConnection.CreateNewCloudProxyAsync(tokenProvider);
cloudConnection.cloudProxy = Option.Some(cloudProxy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy
/// </summary>
class CloudConnection : ICloudConnection
{
const uint OperationTimeoutMilliseconds = 20 * 1000; // 20 secs

readonly ITransportSettings[] transportSettingsList;
readonly IMessageConverterProvider messageConverterProvider;
readonly IClientProvider clientProvider;
readonly ICloudListener cloudListener;
readonly TimeSpan idleTimeout;
readonly bool closeOnIdleTimeout;
readonly TimeSpan operationTimeout;
Option<ICloudProxy> cloudProxy;

protected CloudConnection(
Expand All @@ -35,7 +34,8 @@ protected CloudConnection(
IClientProvider clientProvider,
ICloudListener cloudListener,
TimeSpan idleTimeout,
bool closeOnIdleTimeout)
bool closeOnIdleTimeout,
TimeSpan operationTimeout)
{
this.Identity = Preconditions.CheckNotNull(identity, nameof(identity));
this.ConnectionStatusChangedHandler = connectionStatusChangedHandler;
Expand All @@ -46,6 +46,7 @@ protected CloudConnection(
this.idleTimeout = idleTimeout;
this.closeOnIdleTimeout = closeOnIdleTimeout;
this.cloudProxy = Option.None<ICloudProxy>();
this.operationTimeout = operationTimeout;
}

public static async Task<CloudConnection> Create(
Expand All @@ -57,7 +58,8 @@ public static async Task<CloudConnection> Create(
ICloudListener cloudListener,
ITokenProvider tokenProvider,
TimeSpan idleTimeout,
bool closeOnIdleTimeout)
bool closeOnIdleTimeout,
TimeSpan operationTimeout)
{
Preconditions.CheckNotNull(tokenProvider, nameof(tokenProvider));
var cloudConnection = new CloudConnection(
Expand All @@ -68,7 +70,8 @@ public static async Task<CloudConnection> Create(
clientProvider,
cloudListener,
idleTimeout,
closeOnIdleTimeout);
closeOnIdleTimeout,
operationTimeout);
ICloudProxy cloudProxy = await cloudConnection.CreateNewCloudProxyAsync(tokenProvider);
cloudConnection.cloudProxy = Option.Some(cloudProxy);
return cloudConnection;
Expand Down Expand Up @@ -109,7 +112,7 @@ async Task<IClient> ConnectToIoTHub(ITokenProvider newTokenProvider)
Events.AttemptingConnectionWithTransport(this.transportSettingsList, this.Identity);
IClient client = this.clientProvider.Create(this.Identity, newTokenProvider, this.transportSettingsList);

client.SetOperationTimeoutInMilliseconds(OperationTimeoutMilliseconds);
client.SetOperationTimeoutInMilliseconds((uint)this.operationTimeout.TotalMilliseconds);
client.SetConnectionStatusChangedHandler(this.InternalConnectionStatusChangesHandler);

// TODO: Add support for ProductInfo
Expand All @@ -119,7 +122,7 @@ async Task<IClient> ConnectToIoTHub(ITokenProvider newTokenProvider)
//}

await client.OpenAsync();
Events.CreateDeviceClientSuccess(this.transportSettingsList, OperationTimeoutMilliseconds, this.Identity);
Events.CreateDeviceClientSuccess(this.transportSettingsList, this.operationTimeout, this.Identity);
return client;
}

Expand Down Expand Up @@ -181,12 +184,12 @@ public static void AttemptingConnectionWithTransport(ITransportSettings[] transp
Log.LogInformation((int)EventIds.AttemptingTransport, $"Attempting to connect to IoT Hub for client {identity.Id} via {transportType}...");
}

public static void CreateDeviceClientSuccess(ITransportSettings[] transportSettings, uint timeout, IIdentity identity)
public static void CreateDeviceClientSuccess(ITransportSettings[] transportSettings, TimeSpan timeout, IIdentity identity)
{
string transportType = transportSettings.Length == 1
? TransportName(transportSettings[0].GetTransportType())
: transportSettings.Select(t => TransportName(t.GetTransportType())).Join("/");
Log.LogInformation((int)EventIds.TransportConnected, $"Created cloud proxy for client {identity.Id} via {transportType}, with client operation timeout {timeout}.");
Log.LogInformation((int)EventIds.TransportConnected, $"Created cloud proxy for client {identity.Id} via {transportType}, with client operation timeout {timeout.TotalSeconds} seconds.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class CloudConnectionProvider : ICloudConnectionProvider
readonly bool closeOnIdleTimeout;
readonly ICredentialsCache credentialsCache;
readonly IIdentity edgeHubIdentity;
readonly TimeSpan operationTimeout;
Option<IEdgeHub> edgeHub;

public CloudConnectionProvider(IMessageConverterProvider messageConverterProvider,
Expand All @@ -47,7 +48,8 @@ public CloudConnectionProvider(IMessageConverterProvider messageConverterProvide
ICredentialsCache credentialsCache,
IIdentity edgeHubIdentity,
TimeSpan idleTimeout,
bool closeOnIdleTimeout)
bool closeOnIdleTimeout,
TimeSpan operationTimeout)
{
Preconditions.CheckRange(connectionPoolSize, 1, nameof(connectionPoolSize));
this.messageConverterProvider = Preconditions.CheckNotNull(messageConverterProvider, nameof(messageConverterProvider));
Expand All @@ -60,6 +62,7 @@ public CloudConnectionProvider(IMessageConverterProvider messageConverterProvide
this.deviceScopeIdentitiesCache = Preconditions.CheckNotNull(deviceScopeIdentitiesCache, nameof(deviceScopeIdentitiesCache));
this.credentialsCache = Preconditions.CheckNotNull(credentialsCache, nameof(credentialsCache));
this.edgeHubIdentity = Preconditions.CheckNotNull(edgeHubIdentity, nameof(edgeHubIdentity));
this.operationTimeout = operationTimeout;
}

public void BindEdgeHub(IEdgeHub edgeHubInstance)
Expand Down Expand Up @@ -136,7 +139,8 @@ public async Task<Try<ICloudConnection>> Connect(IClientCredentials clientCreden
cloudListener,
this.edgeHubTokenProvider,
this.idleTimeout,
this.closeOnIdleTimeout);
this.closeOnIdleTimeout,
this.operationTimeout);
Events.SuccessCreatingCloudConnection(clientCredentials.Identity);
return Try.Success(cc);
}
Expand All @@ -150,7 +154,8 @@ public async Task<Try<ICloudConnection>> Connect(IClientCredentials clientCreden
this.clientProvider,
cloudListener,
this.idleTimeout,
this.closeOnIdleTimeout);
this.closeOnIdleTimeout,
this.operationTimeout);
Events.SuccessCreatingCloudConnection(clientCredentials.Identity);
return Try.Success(cc);
}
Expand Down Expand Up @@ -188,7 +193,8 @@ public async Task<Try<ICloudConnection>> Connect(IIdentity identity, Action<stri
cloudListener,
this.edgeHubTokenProvider,
this.idleTimeout,
this.closeOnIdleTimeout);
this.closeOnIdleTimeout,
this.operationTimeout);
Events.SuccessCreatingCloudConnection(identity);
return Try.Success(cc);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ void RegisterRoutingModule(ContainerBuilder builder, (bool isEnabled, bool usePe
int cloudConnectionIdleTimeoutSecs = this.configuration.GetValue("CloudConnectionIdleTimeoutSecs", 3600);
TimeSpan cloudConnectionIdleTimeout = TimeSpan.FromSeconds(cloudConnectionIdleTimeoutSecs);
bool closeCloudConnectionOnIdleTimeout = this.configuration.GetValue("CloseCloudConnectionOnIdleTimeout", true);
int cloudOperationTimeoutSecs = this.configuration.GetValue("CloudOperationTimeoutSecs", 20);
TimeSpan cloudOperationTimeout = TimeSpan.FromSeconds(cloudOperationTimeoutSecs);

builder.RegisterModule(
new RoutingModule(
Expand All @@ -136,7 +138,8 @@ void RegisterRoutingModule(ContainerBuilder builder, (bool isEnabled, bool usePe
connectivityCheckFrequency,
maxConnectedClients,
cloudConnectionIdleTimeout,
closeCloudConnectionOnIdleTimeout));
closeCloudConnectionOnIdleTimeout,
cloudOperationTimeout));
}

void RegisterCommonModule(ContainerBuilder builder, bool optimizeForPerformance, (bool isEnabled, bool usePersistentStorage, StoreAndForwardConfiguration config, string storagePath) storeAndForward)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class RoutingModule : Module
readonly int maxConnectedClients;
readonly TimeSpan cloudConnectionIdleTimeout;
readonly bool closeCloudConnectionOnIdleTimeout;
readonly TimeSpan operationTimeout;

public RoutingModule(string iotHubName,
string edgeDeviceId,
Expand All @@ -56,7 +57,8 @@ public RoutingModule(string iotHubName,
TimeSpan connectivityCheckFrequency,
int maxConnectedClients,
TimeSpan cloudConnectionIdleTimeout,
bool closeCloudConnectionOnIdleTimeout)
bool closeCloudConnectionOnIdleTimeout,
TimeSpan operationTimeout)
{
this.iotHubName = Preconditions.CheckNonWhiteSpace(iotHubName, nameof(iotHubName));
this.edgeDeviceId = Preconditions.CheckNonWhiteSpace(edgeDeviceId, nameof(edgeDeviceId));
Expand All @@ -73,6 +75,7 @@ public RoutingModule(string iotHubName,
this.maxConnectedClients = Preconditions.CheckRange(maxConnectedClients, 1);
this.cloudConnectionIdleTimeout = cloudConnectionIdleTimeout;
this.closeCloudConnectionOnIdleTimeout = closeCloudConnectionOnIdleTimeout;
this.operationTimeout = operationTimeout;
}

protected override void Load(ContainerBuilder builder)
Expand Down Expand Up @@ -184,7 +187,8 @@ protected override void Load(ContainerBuilder builder)
credentialsCache,
edgeHubCredentials.Identity,
this.cloudConnectionIdleTimeout,
this.closeCloudConnectionOnIdleTimeout);
this.closeCloudConnectionOnIdleTimeout,
this.operationTimeout);
return cloudConnectionProvider;
})
.As<Task<ICloudConnectionProvider>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test
public class ClientTokenCloudConnectionTest
{
static readonly ITokenProvider TokenProvider = Mock.Of<ITokenProvider>();
static readonly IDeviceScopeIdentitiesCache DeviceScopeIdentitiesCache = Mock.Of<IDeviceScopeIdentitiesCache>();
static readonly IDeviceScopeIdentitiesCache DeviceScopeIdentitiesCache = Mock.Of<IDeviceScopeIdentitiesCache>();

[Unit]
[Fact]
Expand All @@ -40,7 +40,8 @@ public async Task GetCloudConnectionForIdentityWithTokenTest()
clientProvider,
Mock.Of<ICloudListener>(),
TimeSpan.FromMinutes(60),
true);
true,
TimeSpan.FromSeconds(20));
Option<ICloudProxy> cloudProxy1 = cloudConnection.CloudProxy;
Assert.True(cloudProxy1.HasValue);
Assert.True(cloudProxy1.OrDefault().IsActive);
Expand Down Expand Up @@ -74,7 +75,8 @@ public async Task UpdateInvalidIdentityWithTokenTest()
deviceClientProvider.Object,
Mock.Of<ICloudListener>(),
TimeSpan.FromMinutes(60),
true);
true,
TimeSpan.FromSeconds(20));

Option<ICloudProxy> cloudProxy1 = cloudConnection.CloudProxy;
Assert.True(cloudProxy1.HasValue);
Expand Down Expand Up @@ -116,7 +118,8 @@ ITokenCredentials GetClientCredentialsWithNonExpiringToken()
deviceClientProvider.Object,
Mock.Of<ICloudListener>(),
TimeSpan.FromMinutes(60),
true);
true,
TimeSpan.FromSeconds(20));

Option<ICloudProxy> cloudProxy = cloudConnection.CloudProxy;
Assert.True(cloudProxy.HasValue);
Expand Down Expand Up @@ -163,8 +166,9 @@ ITokenCredentials GetClientCredentialsWithNonExpiringToken()
clientProvider,
Mock.Of<ICloudListener>(),
TimeSpan.FromMinutes(60),
true);

true,
TimeSpan.FromSeconds(20));

Option<ICloudProxy> cloudProxy1 = cloudConnection.CloudProxy;
Assert.True(cloudProxy1.HasValue);
Assert.True(cloudProxy1.OrDefault().IsActive);
Expand Down Expand Up @@ -229,7 +233,8 @@ ITokenCredentials GetClientCredentialsWithNonExpiringToken()
clientProvider,
Mock.Of<ICloudListener>(),
TimeSpan.FromMinutes(60),
true);
true,
TimeSpan.FromSeconds(20));

Option<ICloudProxy> cloudProxy1 = cloudConnection.CloudProxy;
Assert.True(cloudProxy1.HasValue);
Expand Down Expand Up @@ -295,11 +300,12 @@ IClient GetMockedDeviceClient()
.Callback<ConnectionStatusChangesHandler>(c => connectionStatusChangesHandler = c);

deviceClient.Setup(dc => dc.OpenAsync())
.Callback(() =>
{
Assert.NotNull(connectionStatusChangesHandler);
connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok);
})
.Callback(
() =>
{
Assert.NotNull(connectionStatusChangesHandler);
connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok);
})
.Returns(Task.CompletedTask);
return deviceClient.Object;
}
Expand Down Expand Up @@ -328,7 +334,8 @@ void ConnectionStatusHandler(string id, CloudConnectionStatus status)
deviceClientProvider.Object,
Mock.Of<ICloudListener>(),
TimeSpan.FromMinutes(60),
true);
true,
TimeSpan.FromSeconds(20));

Assert.Equal(receivedConnectedStatusCount, 1);
Option<ICloudProxy> cloudProxy1 = cloudConnection.CloudProxy;
Expand Down Expand Up @@ -387,13 +394,14 @@ IClient GetMockedDeviceClient()
.Callback<ConnectionStatusChangesHandler>(c => connectionStatusChangesHandler = c);

deviceClient.Setup(dc => dc.OpenAsync())
.Callback(() =>
{
int currentCount = receivedConnectedStatusCount;
Assert.NotNull(connectionStatusChangesHandler);
connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok);
Assert.Equal(receivedConnectedStatusCount, currentCount);
})
.Callback(
() =>
{
int currentCount = receivedConnectedStatusCount;
Assert.NotNull(connectionStatusChangesHandler);
connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok);
Assert.Equal(receivedConnectedStatusCount, currentCount);
})
.Returns(Task.CompletedTask);
return deviceClient.Object;
}
Expand All @@ -407,9 +415,18 @@ IClient GetMockedDeviceClient()
var messageConverterProvider = Mock.Of<IMessageConverterProvider>();

var credentialsCache = Mock.Of<ICredentialsCache>();
ICloudConnectionProvider cloudConnectionProvider = new CloudConnectionProvider(messageConverterProvider, 1, deviceClientProvider.Object, Option.None<UpstreamProtocol>(), TokenProvider, DeviceScopeIdentitiesCache, credentialsCache,
ICloudConnectionProvider cloudConnectionProvider = new CloudConnectionProvider(
messageConverterProvider,
1,
deviceClientProvider.Object,
Option.None<UpstreamProtocol>(),
TokenProvider,
DeviceScopeIdentitiesCache,
credentialsCache,
Mock.Of<IIdentity>(i => i.Id == $"{deviceId}/$edgeHub"),
TimeSpan.FromMinutes(60), true);
TimeSpan.FromMinutes(60),
true,
TimeSpan.FromSeconds(20));
cloudConnectionProvider.BindEdgeHub(Mock.Of<IEdgeHub>());
IConnectionManager connectionManager = new ConnectionManager(cloudConnectionProvider, Mock.Of<ICredentialsCache>(), new IdentityProvider(hostname));

Expand Down Expand Up @@ -467,7 +484,8 @@ static IClient GetMockDeviceClient()
return deviceClient.Object;
}

static ITokenCredentials GetMockClientCredentialsWithToken(string hostname = "dummy.azure-devices.net",
static ITokenCredentials GetMockClientCredentialsWithToken(
string hostname = "dummy.azure-devices.net",
string deviceId = "device1")
{
string token = TokenHelper.CreateSasToken(hostname);
Expand Down
Loading

0 comments on commit 6102e31

Please sign in to comment.