From 027a509549a248647ed41ca7fe1dc508771c8123 Mon Sep 17 00:00:00 2001 From: Philip Lin Date: Fri, 4 Jan 2019 13:42:31 -0800 Subject: [PATCH] Code formating for C# codebase (#677) --- .gitignore | 1 - Azure.IoT.Edge.DotSettings | 853 +++++++ Microsoft.Azure.Devices.Edge.sln.DotSettings | 117 +- .../Agent.cs | 245 +- .../AgentEventIds.cs | 2 +- .../DeploymentConfig.cs | 44 +- .../DeploymentConfigInfo.cs | 10 +- .../DeploymentStatus.cs | 24 +- .../Diff.cs | 22 +- .../EmptyModuleSetException.cs | 6 +- .../EnvVal.cs | 8 +- .../ICombinedConfigProvider.cs | 2 +- .../IConfigSource.cs | 4 +- .../IEnvironment.cs | 2 +- .../IModule.cs | 8 +- .../IPlanner.cs | 3 +- .../IRuntimeInfo.cs | 6 +- .../IRuntimeInfoProvider.cs | 4 +- .../IRuntimeStatusModule.cs | 2 +- .../IdentityProviderServiceCredentials.cs | 3 +- ...osoft.Azure.Devices.Edge.Agent.Core.csproj | 7 + .../ModuleConnectionStringBuilder.cs | 5 +- .../ModuleIdentityHelper.cs | 3 +- .../ModuleRuntimeInfo.cs | 21 +- .../ModuleSet.cs | 24 +- .../ModuleState.cs | 7 +- .../ModuleWithIdentity.cs | 8 +- .../NullEnvironment.cs | 8 +- .../Plan.cs | 8 +- .../RestartPolicyManager.cs | 14 +- .../SystemModules.cs | 24 +- .../UnknownModule.cs | 17 +- .../commands/AddToStoreCommand.cs | 1 - .../commands/GroupCommand.cs | 2 +- .../commands/NullCommand.cs | 4 +- .../commands/NullCommandFactory.cs | 4 +- .../commands/ParallelGroupCommand.cs | 2 +- .../commands/RemoveFromStoreCommand.cs | 3 +- .../configsources/ConfigEmptyException.cs | 6 +- .../configsources/ConfigFormatException.cs | 6 +- .../configsources/FileBackupConfigSource.cs | 60 +- .../configsources/FileBackupException.cs | 6 +- .../configsources/FileConfigSource.cs | 24 +- .../InvalidSchemaVersionException.cs | 6 +- .../planners/HealthRestartPlanner.cs | 306 ++- .../planners/RestartPlanner.cs | 26 +- .../planrunners/OrderedPlanRunner.cs | 3 +- .../planrunners/OrdererdRetryPlanRunner.cs | 44 +- .../reporters/NullReporter.cs | 5 +- .../serde/DiffSerde.cs | 9 +- .../serde/ISerde.cs | 5 +- .../serde/ModuleSerde.cs | 17 +- .../serde/ModuleSetSerde.cs | 48 +- .../serde/TypeSpecificSerDe.cs | 9 +- .../CombinedDockerConfig.cs | 14 +- .../Constants.cs | 2 +- .../DockerCommandFactory.cs | 30 +- .../DockerConfig.cs | 97 +- .../DockerDesiredModule.cs | 29 +- .../DockerEnvironment.cs | 52 +- .../DockerEnvironmentProvider.cs | 21 +- .../DockerLoggingConfig.cs | 32 +- .../DockerModule.cs | 54 +- .../DockerPlatformInfo.cs | 16 +- .../DockerReportedConfig.cs | 9 +- .../DockerReportedRuntimeInfo.cs | 18 +- .../DockerReportedUnknownRuntimeInfo.cs | 18 +- .../DockerRuntimeConfig.cs | 11 +- .../DockerRuntimeInfo.cs | 22 +- .../DockerRuntimeModule.cs | 91 +- .../DockerUtil.cs | 34 +- .../EdgeAgentDockerModule.cs | 2 +- .../EdgeAgentDockerRuntimeModule.cs | 31 +- .../EdgeHubDockerModule.cs | 29 +- .../EdgeHubDockerRuntimeModule.cs | 93 +- ...oft.Azure.Devices.Edge.Agent.Docker.csproj | 7 + .../RuntimeInfoProvider.cs | 90 +- .../commands/CreateCommand.cs | 76 +- .../commands/PullCommand.cs | 15 +- .../commands/RemoveCommand.cs | 37 +- .../CombinedEdgeletConfigProvider.cs | 53 +- ...e.Devices.Edge.Agent.Edgelet.Docker.csproj | 7 + .../EdgeletCommandFactory.cs | 34 +- ...ft.Azure.Devices.Edge.Agent.Edgelet.csproj | 7 + .../ModuleIdentityLifecycleManager.cs | 8 +- .../ModuleManagementHttpClient.cs | 54 +- .../RuntimeInfoProvider.cs | 17 +- .../commands/CreateOrUpdateCommand.cs | 45 +- .../AgentState.cs | 45 +- .../EdgeAgentConnection.cs | 119 +- .../IEdgeAgentConnection.cs | 4 +- ...oft.Azure.Devices.Edge.Agent.IoTHub.csproj | 7 + .../ModuleClient.cs | 87 +- .../ModuleIdentityLifecycleManager.cs | 33 +- .../RetryingServiceClient.cs | 16 +- .../RuntimePlatform.cs | 22 +- .../ServiceClient.cs | 5 +- .../configsources/TwinConfigSource.cs | 2 +- .../reporters/IoTHubReporter.cs | 199 +- ...ft.Azure.Devices.Edge.Agent.Service.csproj | 8 + .../Program.cs | 12 +- .../modules/AgentModule.cs | 154 +- .../modules/DockerModule.cs | 53 +- .../modules/EdgeletModule.cs | 41 +- .../modules/FileConfigSourceModule.cs | 21 +- .../modules/LoggingModule.cs | 2 +- .../modules/TwinConfigSourceModule.cs | 28 +- .../AgentTests.cs | 111 +- .../DeploymentConfigInfoTest.cs | 8 +- .../DiffTest.cs | 42 +- ....Azure.Devices.Edge.Agent.Core.Test.csproj | 7 + .../ModuleConnectionStringBuilderTest.cs | 5 +- ...oduleIdentityProviderServiceBuilderTest.cs | 7 +- .../ModuleSetTest.cs | 149 +- .../NullEnvironmentTest.cs | 1 - .../OrderedPlanRunnerTest.cs | 63 +- .../OrderedRetryPlanRunnerTest.cs | 56 +- .../RestartPolicyManagerTest.cs | 1193 ++++----- .../TestCommand.cs | 42 +- .../TestModule.cs | 104 +- .../TestModuleTest.cs | 41 +- .../TestRuntimeModule.cs | 78 +- .../TestRuntimeModuleTest.cs | 11 +- .../UpstreamProtocolTest.cs | 2 +- .../commands/GroupCommandTest.cs | 209 +- .../commands/LoggingCommandFactoryTest.cs | 129 +- .../commands/NullCommandFactoryTest.cs | 2 +- .../commands/NullCommandTest.cs | 4 +- .../commands/ParallelGroupCommandTest.cs | 261 +- .../FileBackupConfigSourceTest.cs | 6 +- .../configsources/FileConfigSourceTest.cs | 259 +- .../planners/HealthRestartPlannerTest.cs | 2137 +++++++++++++---- .../planners/RestartPlannerTest.cs | 2 +- .../AgentTests.cs | 134 +- ....Devices.Edge.Agent.Docker.E2E.Test.csproj | 7 + .../RunCommandValidator.cs | 12 +- .../DockerConfigTest.cs | 5 +- .../DockerEnvironmentProviderTest.cs | 3 - .../DockerEnvironmentTest.cs | 106 +- .../DockerHelper.cs | 33 +- .../DockerLoggingConfigTest.cs | 8 +- .../DockerModuleTest.cs | 54 +- .../DockerRuntimeModuleTest.cs | 103 +- .../DockerUtilTest.cs | 5 +- .../EdgeAgentDockerModuleTest.cs | 12 +- .../EdgeAgentDockerRuntimeModuleTest.cs | 100 +- .../EdgeHubDockerRuntimeModuleTest.cs | 82 +- ...zure.Devices.Edge.Agent.Docker.Test.csproj | 7 + .../RuntimeInfoProviderTest.cs | 44 +- .../commands/CreateCommandTest.cs | 80 +- .../commands/PullCommandTest.cs | 89 +- .../CombinedEdgeletConfigProviderTest.cs | 19 +- ...ices.Edge.Agent.Edgelet.Docker.Test.csproj | 8 + .../EdgeletFixture.cs | 27 +- ...ure.Devices.Edge.Agent.Edgelet.Test.csproj | 7 + .../ModuleIdentityLifecycleManagerTest.cs | 33 +- .../ModuleManagementHttpClientTest.cs | 7 +- .../RuntimeInfoProviderTest.cs | 6 +- .../TestServer/EdgeletTestImplementation.cs | 33 +- .../TestServer/Startup.cs | 2 +- .../AgentStateTest.cs | 36 +- .../EdgeAgentConnectionTest.cs | 1140 ++++----- ...zure.Devices.Edge.Agent.IoTHub.Test.csproj | 7 + .../ModuleClientTest.cs | 33 +- .../ModuleIdentityLifecycleManagerTest.cs | 15 +- .../reporters/IoTHubReporterTest.cs | 1389 ++++++----- .../AmqpConnectionUtils.cs | 1 + .../AmqpEventIds.cs | 2 +- .../AmqpExceptionsHelper.cs | 3 +- .../AmqpMessageConverter.cs | 2 +- .../AmqpMessageUtils.cs | 30 +- .../AmqpProtocolHead.cs | 29 +- .../AmqpRuntimeProvider.cs | 72 +- .../AmqpTwinMessageConverter.cs | 1 + .../AmqpWebSocketListener.cs | 49 +- .../CbsNode.cs | 192 +- .../ClientConnectionHandler.cs | 74 +- .../ClientConnectionsHandler.cs | 2 +- .../Constants.cs | 8 +- .../EdgeAmqpConnection.cs | 6 +- .../EdgeAmqpException.cs | 3 +- .../EdgeAmqpLink.cs | 20 +- .../EdgeAmqpSession.cs | 2 +- .../EdgeSaslPlainAuthenticator.cs | 4 +- .../EdgeTlsTransport.cs | 16 +- .../EdgeX509Principal.cs | 7 +- .../IAmqpConnection.cs | 6 +- .../IAmqpLink.cs | 14 +- .../IAmqpSession.cs | 2 +- ...crosoft.Azure.Devices.Edge.Hub.Amqp.csproj | 8 + .../SaslIdentityHelper.cs | 1 - .../SaslIdentityType.cs | 1 - .../ServerWebSocketTransport.cs | 190 +- .../UriPathTemplate.cs | 19 +- .../linkhandlers/CbsLinkHandler.cs | 6 +- .../linkhandlers/EventsLinkHandler.cs | 32 +- .../linkhandlers/ILinkHandler.cs | 8 +- .../linkhandlers/LinkHandler.cs | 31 +- .../linkhandlers/LinkHandlerProvider.cs | 40 +- .../MethodReceivingLinkHandler.cs | 4 +- .../linkhandlers/MethodSendingLinkHandler.cs | 4 +- .../linkhandlers/ReceivingLinkHandler.cs | 14 +- .../linkhandlers/SendingLinkHandler.cs | 103 +- .../linkhandlers/TwinReceivingLinkHandler.cs | 9 +- .../linkhandlers/TwinSendingLinkHandler.cs | 4 +- .../settings/AmqpSettingsProvider.cs | 2 +- .../settings/DefaultTransportSettings.cs | 1 + .../ClientProvider.cs | 2 + .../ClientTokenCloudConnection.cs | 64 +- .../CloudConnection.cs | 70 +- .../CloudConnectionProvider.cs | 139 +- .../CloudProxy.cs | 134 +- .../CloudProxyEventIds.cs | 2 +- .../ConnectivityAwareClient.cs | 66 +- .../ConnectivityAwareClientProvider.cs | 1 + .../DeviceClientWrapper.cs | 1 - .../DeviceConnectivityManager.cs | 105 +- .../DeviceScopeApiClient.cs | 29 +- ...t.Azure.Devices.Edge.Hub.CloudProxy.csproj | 7 + .../ModuleClientWrapper.cs | 2 +- .../ServiceIdentityHelpers.cs | 1 + .../ServiceProxy.cs | 157 +- .../TwinCollectionMessageConverter.cs | 11 +- .../TwinMessageConverter.cs | 1 + .../authenticators/CloudTokenAuthenticator.cs | 2 +- .../DeviceScopeTokenAuthenticator.cs | 127 +- .../authenticators/TokenCacheAuthenticator.cs | 5 +- .../Authenticator.cs | 2 +- .../ConnectionManager.cs | 236 +- .../ConnectionProvider.cs | 4 +- .../ConnectionReauthenticator.cs | 29 +- .../DeviceScopeAuthenticator.cs | 2 +- .../DeviceScopeIdentitiesCache.cs | 183 +- .../DirectMethodRequest.cs | 5 +- .../EdgeHubConnection.cs | 406 ++-- .../EdgeHubConnectionException.cs | 10 +- .../EdgeHubIOException.cs | 8 +- .../EdgeHubMessageTooLargeException.cs | 6 +- .../EdgeHubTimeoutException.cs | 6 +- .../EdgeMessage.cs | 21 +- .../HubCoreEventIds.cs | 39 +- .../IConnectionManager.cs | 16 +- .../IDeviceConnectivityManager.cs | 8 +- .../IDeviceScopeIdentitiesCache.cs | 8 +- .../IEdgeHub.cs | 44 +- .../IInvokeMethodHandler.cs | 2 +- .../IServiceIdentitiesIterator.cs | 4 +- .../ITwinManager.cs | 10 +- .../IWebSocketListener.cs | 22 +- .../InvokeMethodHandler.cs | 9 +- .../MessageConverterProvider.cs | 8 +- ...crosoft.Azure.Devices.Edge.Hub.Core.csproj | 7 + .../MultipleConnectionsException.cs | 7 +- .../NullDeviceScopeIdentitiesCache.cs | 16 +- .../PersistedTokenCredentialsCache.cs | 62 +- .../SystemProperties.cs | 36 +- .../TwinInfo.cs | 8 +- .../TwinManager.cs | 567 +++-- .../TwinNotFoundException.cs | 8 +- .../cloud/CloudListener.cs | 24 +- .../cloud/ICloudProxy.cs | 1 - .../config/ConfigUpdater.cs | 22 +- .../config/DeviceConnectionStatus.cs | 4 +- .../config/LocalConfigSource.cs | 9 +- .../device/DeviceMessageHandler.cs | 204 +- .../device/IDeviceListener.cs | 4 +- .../device/IDeviceProxy.cs | 9 +- .../identity/ICertificateCredentials.cs | 1 + .../identity/ModuleIdentity.cs | 3 +- .../identity/service/ServiceAuthentication.cs | 1 - .../identity/service/ServiceIdentity.cs | 2 +- .../identity/service/ServiceIdentityStatus.cs | 1 + .../service/SymmetricKeyAuthentication.cs | 10 +- .../routing/CloudEndpoint.cs | 152 +- .../routing/EndpointFactory.cs | 8 +- .../routing/ModuleEndpoint.cs | 152 +- .../routing/RoutingEdgeHub.cs | 246 +- .../routing/RoutingMessageConverter.cs | 3 +- .../storage/CheckpointStore.cs | 13 +- .../storage/MessageStore.cs | 323 +-- .../HttpsExtensionConnectionAdapter.cs | 132 +- .../ExceptionFilter.cs | 10 +- .../Extensions/HttpContextExtensions.cs | 2 +- .../HttpEventIds.cs | 2 +- .../HttpProtocolHead.cs | 2 +- .../MethodRequest.cs | 16 +- .../MethodRequestValidator.cs | 12 +- ...crosoft.Azure.Devices.Edge.Hub.Http.csproj | 7 + .../WebSocketListenerRegistry.cs | 2 +- .../controllers/TwinsController.cs | 46 +- .../middleware/AuthenticationMiddleware.cs | 11 +- .../middleware/WebSocketHandlingMiddleware.cs | 7 +- .../ByteBufferConverter.cs | 1 - .../DeviceIdentityProvider.cs | 22 +- .../DeviceProxy.cs | 21 +- .../MessageAddressConversionConfiguration.cs | 11 +- .../MessageAddressConverter.cs | 14 +- .../MessagingServiceClient.cs | 47 +- ...crosoft.Azure.Devices.Edge.Hub.Mqtt.csproj | 7 + .../MqttConnectionProvider.cs | 4 +- .../MqttEventIds.cs | 2 +- .../MqttFeedbackMessage.cs | 3 +- .../MqttProtocolHead.cs | 17 +- .../MqttSettingsProvider.cs | 4 +- .../MqttWebSocketListener.cs | 4 +- .../ProtocolGatewayIdentity.cs | 3 +- .../ProtocolGatewayMessage.cs | 41 +- .../ProtocolGatewayMessageConverter.cs | 10 +- .../ServerWebSocketChannel.cs | 90 +- .../ServerWebSocketChannelConfig.cs | 42 +- .../SessionState.cs | 10 +- .../SessionStatePersistenceProvider.cs | 23 +- .../SessionStateStoragePersistenceProvider.cs | 9 +- .../TwinAddressHelper.cs | 66 +- .../CertificateRenewal.cs | 50 +- .../Constants.cs | 19 +- .../DependencyManager.cs | 10 +- .../EdgeHubCertificates.cs | 27 +- .../Hosting.cs | 45 +- ...soft.Azure.Devices.Edge.Hub.Service.csproj | 7 + .../Startup.cs | 35 +- .../modules/AmqpModule.cs | 91 +- .../modules/CommonModule.cs | 227 +- .../modules/MqttModule.cs | 67 +- .../modules/RoutingModule.cs | 452 ++-- .../Dispatcher.cs | 141 +- .../Endpoint.cs | 25 +- .../EndpointHealthData.cs | 8 +- .../Evaluator.cs | 75 +- .../IMessage.cs | 2 +- .../IMessageStore.cs | 8 +- .../InvalidDetails.cs | 8 +- .../Message.cs | 68 +- ...icrosoft.Azure.Devices.Routing.Core.csproj | 8 + .../NullRoutingPerfCounter.cs | 6 +- .../NullRoutingUserMetricLogger.cs | 6 +- .../NullUserAnalyticsLogger.cs | 22 +- .../Route.cs | 24 +- .../RouteFactory.cs | 12 +- .../RouteStore.cs | 11 +- .../Router.cs | 81 +- .../RouterConfig.cs | 22 +- .../Routing.cs | 40 +- .../RoutingIdBuilder.cs | 14 +- .../SendFailureDetails.cs | 8 +- .../SinkResult.cs | 24 +- .../Source.cs | 9 +- .../checkpointers/CheckpointData.cs | 5 +- .../checkpointers/Checkpointer.cs | 26 +- .../checkpointers/CheckpointerStatus.cs | 12 +- .../checkpointers/InMemoryCheckpointStore.cs | 2 +- .../checkpointers/MasterCheckpointer.cs | 88 +- .../checkpointers/NullCheckpointStore.cs | 14 +- .../endpoints/AsyncEndpointExecutor.cs | 106 +- .../endpoints/AsyncEndpointExecutorFactory.cs | 4 +- .../endpoints/AsyncEndpointExecutorOptions.cs | 10 +- .../endpoints/ConsoleEndpoint.cs | 10 +- .../endpoints/EndpointExecutorConfig.cs | 18 +- .../endpoints/EndpointExecutorStatus.cs | 26 +- .../endpoints/NullEndpoint.cs | 8 +- .../endpoints/StoringAsyncEndpointExecutor.cs | 169 +- .../StoringAsyncEndpointExecutorFactory.cs | 2 +- .../endpoints/SyncEndpointExecutor.cs | 15 +- .../endpoints/SyncEndpointExecutorFactory.cs | 4 +- .../statemachine/EndpointExecutorFsm.cs | 301 ++- .../endpoints/statemachine/ICommand.cs | 68 +- .../endpoints/statemachine/StateActions.cs | 10 +- .../statemachine/StateCommandPair.cs | 22 +- .../endpoints/statemachine/StateTransition.cs | 10 +- .../messageSources/BaseMessageSource.cs | 14 +- .../messageSources/IMessageSource.cs | 4 +- .../messageSources/MessageSourceExtensions.cs | 4 +- .../query/Bool.cs | 7 +- .../query/CompareOp.cs | 2 +- .../query/ComparisonOperators.cs | 6 + .../query/ConditionVisitor.cs | 123 +- .../query/ErrorListener.cs | 20 +- .../query/RouteCompilationException.cs | 8 +- .../query/Undefined.cs | 5 +- .../query/builtins/Abs.cs | 1 - .../query/builtins/AnyArgs.cs | 8 +- .../query/builtins/Args.cs | 17 +- .../query/builtins/BodyQuery.cs | 12 +- .../query/builtins/Builtin.cs | 26 +- .../query/builtins/Concat.cs | 4 +- .../query/builtins/TwinChangeIncludes.cs | 9 +- .../query/builtins/VarArgs.cs | 12 +- .../query/errors/CompilationError.cs | 21 +- .../query/errors/ErrorPosition.cs | 23 +- .../query/errors/ErrorRange.cs | 20 +- .../query/jsonpath/JsonPathValidator.cs | 2 +- .../jsonpath/TwinChangeJsonPathValidator.cs | 16 +- .../query/types/QueryValue.cs | 32 +- .../query/types/QueryValueType.cs | 6 +- .../services/FilteringRoutingService.cs | 48 +- .../services/FrontendRoutingService.cs | 61 +- .../sinks/RetryingSink.cs | 3 +- .../util/CollectionEx.cs | 1 + .../util/Option.cs | 28 +- .../util/Preconditions.cs | 31 +- .../util/concurrency/AsyncLock.cs | 17 +- .../util/concurrency/AtomicBoolean.cs | 17 +- .../util/concurrency/AtomicReference.cs | 13 +- .../AmqpDirectMethodMessageConverterTest.cs | 23 +- .../AmqpMessageConverterTest.cs | 41 +- .../AmqpProtocolHeadTest.cs | 97 +- .../AmqpSettingsProviderTest.cs | 7 +- .../AmqpTwinMessageConverterTest.cs | 9 +- .../CbsNodeTest.cs | 2 +- .../ConnectionHandlerTest.cs | 9 +- .../DefaultTransportSettingsTest.cs | 6 +- .../DeviceBoundLinkHandlerTest.cs | 2 +- .../EdgeX509PrincipalTest.cs | 53 +- .../EventsLinkHandlerTest.cs | 35 +- .../LinkHandlerProviderTest.cs | 181 +- ...ft.Azure.Devices.Edge.Hub.Amqp.Test.csproj | 7 + .../ReceivingLinkHandlerTest.cs | 7 +- .../SaslIdentityTest.cs | 25 +- .../SendingLinkHandlerTest.cs | 45 +- .../ServerWebSocketTransportTest.cs | 132 +- .../ClientTokenCloudConnectionTest.cs | 7 +- .../CloudConnectionProviderTest.cs | 163 +- .../CloudConnectionTest.cs | 26 +- .../CloudProxyTest.cs | 83 +- .../CloudProxyUnitTest.cs | 34 +- .../CloudReceiverTest.cs | 80 +- .../ConnectivityAwareClientTest.cs | 20 +- .../DeviceClientMessageConverterTest.cs | 6 +- .../DeviceConnectivityManagerTest.cs | 12 +- .../DeviceScopeApiClientTest.cs | 28 +- ...DeviceScopeCertificateAuthenticatorTest.cs | 258 +- .../DeviceScopeTokenAuthenticatorTest.cs | 2 +- .../EventHubReceiver.cs | 8 +- .../MessageHelper.cs | 10 +- ...re.Devices.Edge.Hub.CloudProxy.Test.csproj | 7 + .../ServiceIdentityHelpersTest.cs | 102 +- .../ServiceProxyTest.cs | 11 +- .../TokenCacheAuthenticatorTest.cs | 2 +- .../TokenHelperTest.cs | 17 +- .../TwinCollectionMessageConverterTest.cs | 14 +- .../TwinMessageConverterTest.cs | 12 +- .../AuthenticatorTest.cs | 9 +- .../ConnectionManagerTest.cs | 39 +- .../ConnectionProviderTest.cs | 1 - .../ConnectionReauthenticatorTest.cs | 16 +- .../CredentialsCacheTest.cs | 2 +- .../DeviceMessageHandlerTest.cs | 17 +- .../DeviceScopeIdentitiesCacheTest.cs | 14 +- .../EdgeHubConfigTest.cs | 2 +- .../EdgeHubConnectionTest.cs | 3 +- .../IdentityFactoryTest.cs | 17 +- .../InvokeMethodHandlerTest.cs | 8 +- .../IotHubConnectionExceptionTest.cs | 1 - .../MessageConverterProviderTest.cs | 26 +- .../MessageTest.cs | 16 +- ...ft.Azure.Devices.Edge.Hub.Core.Test.csproj | 7 + .../ServiceIdentityTest.cs | 12 +- .../TwinManagerTest.cs | 443 +++- .../routing/CloudMessageProcessorTests.cs | 13 +- .../routing/EndpointFactoryTest.cs | 2 +- .../routing/ModuleEndpointTest.cs | 11 +- .../routing/RoutingEdgeHubTest.cs | 114 +- .../routing/RoutingMessageConverterTest.cs | 89 +- .../routing/RoutingTest.cs | 207 +- .../storage/MessageStoreTest.cs | 98 +- .../Cloud2DeviceTest.cs | 23 +- .../DependencyManager.cs | 80 +- .../EdgeHubConnectionTest.cs | 12 +- .../EdgeToDeviceMethodTest.cs | 21 +- ...oft.Azure.Devices.Edge.Hub.E2E.Test.csproj | 7 + .../ProtocolHeadFixture.cs | 37 +- .../RegistryManagerHelper.cs | 27 +- .../StressTest.cs | 25 +- .../TelemetryTest.cs | 12 +- .../TestModule.cs | 49 +- .../TestSettings.cs | 9 +- .../TwinDiffE2ETest.cs | 10 +- .../AuthenticationMiddlewareTest.cs | 15 +- .../HttpsExtensionConnectionAdapterTest.cs | 10 +- .../MethodRequestTest.cs | 30 +- .../MethodRequestValidatorTest.cs | 17 +- ...ft.Azure.Devices.Edge.Hub.Http.Test.csproj | 7 + .../TwinsControllerTest.cs | 39 +- .../WebSocketHandlingMiddlewareTest.cs | 112 +- .../WebSocketListenerRegistryTest.cs | 46 +- .../ByteBufferConverterTest.cs | 62 +- .../DeviceIdentityProviderTest.cs | 156 +- .../DeviceProxyTest.cs | 52 +- .../IdentityTest.cs | 164 +- .../MessageAddressConverterTest.cs | 148 +- .../MessagingServiceClientTest.cs | 197 +- ...ft.Azure.Devices.Edge.Hub.Mqtt.Test.csproj | 7 + .../MqttFeedbackMessageTest.cs | 3 +- .../MqttWebSocketListenerTest.cs | 10 +- .../ProtocolGatewayMessageConverterTest.cs | 52 +- .../ServerWebSocketChannelTest.cs | 15 +- .../SessionStatePersistenceProviderTest.cs | 72 +- ...sionStateStoragePersistenceProviderTest.cs | 12 +- .../SessionStateTest.cs | 7 +- .../TwinAddressHelperTests.cs | 9 +- .../DependencyManagerTest.cs | 44 +- ...Azure.Devices.Edge.Hub.Service.Test.csproj | 7 + .../DispatcherTest.cs | 46 +- .../EndpointTest.cs | 13 +- .../EvaluatorTest.cs | 37 +- .../ExponentialBackoffStrategy.cs | 1 - .../MessageQueryTest.cs | 47 +- .../MessageTest.cs | 30 +- ...oft.Azure.Devices.Routing.Core.Test.csproj | 7 + .../NullRoutingPerfCounterTest.cs | 1 - .../NullRoutingUserMetricLoggerTest.cs | 1 - .../NullUserAnalyticsLoggerTest.cs | 1 - .../RouteStoreTest.cs | 16 +- .../RouteTest.cs | 42 +- .../RouterFactoryTest.cs | 5 +- .../RouterTest.cs | 66 +- .../RoutingTestModule.cs | 33 +- .../SinkResultTest.cs | 5 +- .../TestConstants.cs | 7 +- .../TestNotifier.cs | 4 +- .../app.config | 150 +- .../checkpointers/CheckpointerTest.cs | 138 +- .../checkpointers/LoggedCheckpointer.cs | 60 +- .../checkpointers/MasterCheckpointerTest.cs | 23 +- .../checkpointers/NullCheckpointStoreTest.cs | 10 +- .../checkpointers/NullCheckpointerTest.cs | 54 +- .../endpoints/AsyncEndpointExecutorTest.cs | 30 +- .../CheckpointerEndpointExecutorFactory.cs | 2 +- .../endpoints/ConsoleEndpointTest.cs | 15 +- .../endpoints/FailedEndpoint.cs | 19 +- .../endpoints/NullEndpointTest.cs | 5 +- .../endpoints/PartialFailureEndpoint.cs | 70 +- .../endpoints/RevivableEndpoint.cs | 108 +- .../endpoints/StalledEndpoint.cs | 61 +- .../StoringAsyncEndpointExecutorTest.cs | 65 +- .../endpoints/SyncEndpointExecutorTest.cs | 42 +- .../endpoints/TestEndpoint.cs | 34 +- .../statemachine/EndpointExecutorFsmTest.cs | 146 +- .../statemachine/StateCommandPairTest.cs | 10 +- .../jsonpath/JsonPathValidatorTest.cs | 11 +- .../TwinChangeJsonPathValidatorTest.cs | 8 +- .../perfcounters/NullRoutingPerfCounter.cs | 1 + .../query/BodyQueryI18NTest.cs | 18 +- .../query/BodyQueryTest.cs | 59 +- .../query/ConditionCompilerTest.cs | 1507 ++++++------ .../query/ConditionVisitorTest.cs | 15 +- .../query/ErrorListenerTest.cs | 31 +- .../query/NullTest.cs | 17 +- .../query/QueryValueTest.cs | 28 +- .../query/TwinBodyQueryI18NTest.cs | 130 +- .../query/TwinBodyQueryTest.cs | 518 ++-- .../query/TwinChangeIncludesTest.cs | 37 +- .../query/UndefinedTest.cs | 20 +- .../query/errors/CompilationErrorTest.cs | 8 +- .../query/errors/ErrorPositionTest.cs | 8 +- .../query/errors/ErrorRangeTest.cs | 5 +- .../routeFactory/RouteFactoryTest.cs | 369 +-- .../routeFactory/TestRouteFactory.cs | 2 +- .../services/FilteringRoutingServiceTest.cs | 34 +- .../services/FrontendRoutingServiceTest.cs | 9 +- .../sinks/FailedSink.cs | 4 +- .../sinks/FailedSinkFactory.cs | 1 - .../sinks/NullSinkFactoryTest.cs | 7 +- .../sinks/NullSinkTest.cs | 9 +- .../sinks/PartialFailureSink.cs | 5 +- .../sinks/RetryingSinkTest.cs | 18 +- .../sinks/TestSink.cs | 11 +- .../sinks/TestSinkFactory.cs | 5 +- .../sources/NullSourceFactoryTest.cs | 9 +- .../sources/NullSourceTest.cs | 9 +- .../sources/TestSource.cs | 6 +- .../util/CollectionExTest.cs | 13 +- .../util/DateTimeExTest.cs | 21 +- .../util/MockSystemTime.cs | 6 +- .../util/OptionTest.cs | 46 +- .../util/PreconditionsTest.cs | 13 +- .../util/TaskExTest.cs | 8 +- .../util/concurrency/AsyncLockTest.cs | 62 +- .../util/concurrency/AtomicBooleanTest.cs | 16 +- .../DirectMethodReceiver.csproj | 7 + .../DirectMethodReceiver/src/Program.cs | 21 +- .../DirectMethodSender.csproj | 7 + .../config/appsettings.json | 8 +- .../DirectMethodSender/src/Program.cs | 6 +- .../MessagesAnalyzer/MessageDetails.cs | 8 +- .../MessagesAnalyzer/MessagesAnalyzer.csproj | 7 + .../MessagesAnalyzer/MessagesCache.cs | 5 +- .../MessagesAnalyzer/MissedMessagesDetails.cs | 12 +- edge-modules/MessagesAnalyzer/ModuleReport.cs | 11 +- .../PartitionReceiverHandler.cs | 8 +- edge-modules/MessagesAnalyzer/Reporter.cs | 7 +- edge-modules/MessagesAnalyzer/Settings.cs | 8 +- edge-modules/MessagesAnalyzer/Startup.cs | 4 +- .../SimulatedTemperatureSensor.csproj | 7 + .../src/MessageBody.cs | 2 +- .../SimulatedTemperatureSensor/src/Program.cs | 21 +- edge-modules/TemperatureFilter/MessageBody.cs | 2 +- edge-modules/TemperatureFilter/Program.cs | 39 +- .../TemperatureFilter.csproj | 8 + .../AsyncLock.cs | 14 +- .../EdgeHubAsyncCollector.cs | 25 +- ...ft.Azure.WebJobs.Extensions.EdgeHub.csproj | 8 + .../Utils.cs | 2 +- .../bindings/EdgeHubCollectorBuilder.cs | 1 - .../bindings/EdgeHubMessageProcessor.cs | 4 +- .../bindings/EdgeHubTriggerBinding.cs | 12 +- .../config/EdgeHubExtensionConfigProvider.cs | 20 +- .../config/EdgeHubHostConfigExtensions.cs | 1 - .../transientFaultHandling/AsyncExecution.cs | 62 +- .../AsyncExecution[T].cs | 48 +- .../ExponentialBackoff.cs | 4 +- .../transientFaultHandling/FixedInterval.cs | 34 +- .../transientFaultHandling/Guard.cs | 62 +- .../ITransientErrorDetectionStrategy.cs | 4 +- .../transientFaultHandling/Incremental.cs | 22 +- .../RetryLimitExceededException.cs | 16 +- .../transientFaultHandling/RetryPolicy.cs | 184 +- .../transientFaultHandling/RetryStrategy.cs | 24 +- .../RetryingEventArgs.cs | 32 +- .../transientFaultHandling/ShouldRetry.cs | 4 +- .../EdgeHubTriggerCSharp.cs | 40 +- .../EdgeHubTriggerCSharp.csproj | 8 + edge-modules/functions/samples/host.json | 14 +- edge-modules/load-gen/BufferPool.cs | 54 +- edge-modules/load-gen/Program.cs | 19 +- edge-modules/load-gen/Settings.cs | 61 +- edge-modules/load-gen/Timers.cs | 64 +- edge-modules/load-gen/load-gen.csproj | 7 + .../ColumnFamilyDbStore.cs | 43 +- .../ColumnFamilyStorageRocksDbWrapper.cs | 4 +- .../DbStoreProvider.cs | 38 +- ....Azure.Devices.Edge.Storage.RocksDb.csproj | 7 + .../RocksDbOptionsProvider.cs | 16 +- .../RocksDbWrapper.cs | 36 +- .../AssemblyInfo.cs | 1 - .../EncryptedStore.cs | 12 +- .../EntityStore.cs | 36 +- .../ISequentialStore.cs | 4 +- .../IStoreProvider.cs | 1 - .../InMemoryDbStore.cs | 74 +- ...icrosoft.Azure.Devices.Edge.Storage.csproj | 7 + .../SequentialStore.cs | 30 +- .../StoreProvider.cs | 4 +- .../AsyncLockProvider.cs | 4 +- .../CertificateHelper.cs | 98 +- .../CollectionEx.cs | 3 + .../DictionaryComparer.cs | 46 +- .../DiskFile.cs | 2 +- .../ExceptionEx.cs | 3 +- .../Fallback.cs | 9 +- .../HttpClientHelper.cs | 6 +- .../JsonEx.cs | 32 +- .../LinqEx.cs | 12 +- .../Logger.cs | 41 +- .../Metrics.cs | 82 +- .../Microsoft.Azure.Devices.Edge.Util.csproj | 7 + .../Option.cs | 32 +- .../Preconditions.cs | 37 +- .../ResettableTimer.cs | 4 +- .../Retry.cs | 48 +- .../SasTokenHelper.cs | 14 +- .../ShutdownHandler.cs | 39 +- .../StringEx.cs | 2 +- .../TaskEx.cs | 46 +- .../Microsoft.Azure.Devices.Edge.Util/Try.cs | 7 +- .../UtilEventsIds.cs | 2 +- .../VersionInfo.cs | 29 +- .../concurrency/AsyncLock.cs | 14 +- .../concurrency/AtomicBoolean.cs | 9 +- .../concurrency/AtomicLong.cs | 9 +- .../edged/EncryptionProvider.cs | 11 +- .../edged/HttpHsmCommunicationException.cs | 13 +- .../edged/HttpHsmSignatureProvider.cs | 7 +- .../edged/WorkloadClient.cs | 21 +- .../json/OptionConverter.cs | 5 +- .../logging/LoggerEventListener.cs | 5 +- .../transientFaultHandling/AsyncExecution.cs | 75 +- .../AsyncExecution[T].cs | 48 +- .../ExponentialBackoff.cs | 4 +- .../transientFaultHandling/FixedInterval.cs | 34 +- .../transientFaultHandling/Guard.cs | 64 +- .../ITransientErrorDetectionStrategy.cs | 4 +- .../transientFaultHandling/Incremental.cs | 20 +- .../RetryLimitExceededException.cs | 16 +- .../transientFaultHandling/RetryPolicy.cs | 184 +- .../transientFaultHandling/RetryStrategy.cs | 24 +- .../RetryingEventArgs.cs | 32 +- .../transientFaultHandling/ShouldRetry.cs | 4 +- .../uds/HttpBufferedStream.cs | 42 +- .../uds/HttpRequestResponseSerializer.cs | 28 +- .../uds/HttpUdsMessageHandler.cs | 8 +- .../uds/UnixDomainSocketEndPoint.cs | 61 +- .../ColumnFamilyStorageRocksDbWTest.cs | 12 +- .../ColumnFamilyStoreTest.cs | 2 +- .../DbStoreProviderTest.cs | 1 + ...e.Devices.Edge.Storage.RocksDb.Test.csproj | 7 + .../RockDbWrapperTest.cs | 20 +- .../RocksDbOptionsProviderTest.cs | 10 +- .../TestRocksDbStoreProvider.cs | 2 +- .../EncryptedStoreTest.cs | 24 +- .../EntityStoreTest.cs | 2 - .../EntityStoreTestBase.cs | 10 +- .../InMemoryStorePartitionTest.cs | 86 +- ...oft.Azure.Devices.Edge.Storage.Test.csproj | 7 + .../SequentialStoreTest.cs | 2 - .../SequentialStoreTestBase.cs | 13 +- .../SerDeExtensionsTest.cs | 37 +- .../StoreUtilsTest.cs | 1 - .../CertificateHelper.cs | 41 +- .../ClientAssertionCertificate.cs | 8 +- .../ConfigHelper.cs | 1 + .../KeyVaultHelper.cs | 17 +- ...Azure.Devices.Edge.Util.Test.Common.csproj | 7 + .../StringExtensions.cs | 7 +- .../TokenHelper.cs | 9 +- .../settings/linux.json | 4 +- .../CollectionExTest.cs | 5 +- .../DictionaryComparerTest.cs | 2 +- .../DiskFileTest.cs | 6 +- .../ExceptionExTest.cs | 18 +- .../FallbackTest.cs | 53 +- .../HashTest.cs | 1 - .../JsonExTest.cs | 75 +- .../KeyVaultHelperTest.cs | 2 +- .../LinqExTest.cs | 72 +- ...rosoft.Azure.Devices.Edge.Util.Test.csproj | 7 + .../OptionTest.cs | 24 +- .../PreconditionsTest.cs | 4 +- .../PriorityOrderer.cs | 9 +- .../ResettableTimerTest.cs | 7 +- .../RetryTest.cs | 21 +- .../SasTokenHelperTest.cs | 1 - .../TaskExTest.cs | 1 - .../certificate/CertificateHelperTest.cs | 47 +- .../concurrency/AsyncLockTest.cs | 45 +- .../edged/WorkloadClientTest.cs | 8 +- .../json/OptionConverterTest.cs | 77 +- .../uds/HttpBufferedStreamTest.cs | 10 +- .../uds/HttpRequestResponseSerializerTest.cs | 6 +- .../workloadtestserver/Startup.cs | 2 +- .../workloadtestserver/WorkloadFixture.cs | 25 +- .../WorkloadTestImplementation.cs | 5 +- .../IotEdgeQuickstart.csproj | 7 + smoke/IotEdgeQuickstart/Program.cs | 71 +- smoke/IotEdgeQuickstart/Quickstart.cs | 38 +- smoke/IotEdgeQuickstart/details/Details.cs | 262 +- smoke/IotEdgeQuickstart/details/Iotedgectl.cs | 8 +- .../details/IotedgedLinux.cs | 32 +- .../details/IotedgedWindows.cs | 87 +- .../details/PartitionReceiveHandler.cs | 12 +- .../details/RegistryCredentials.cs | 29 +- .../IotEdgeQuickstart/details/YamlDocument.cs | 2 + smoke/LeafDevice/LeafDevice.cs | 13 +- smoke/LeafDevice/LeafDevice.csproj | 7 + smoke/LeafDevice/Program.cs | 17 +- .../details/CustomCertificateValidator.cs | 2 +- smoke/LeafDevice/details/Details.cs | 77 +- .../details/PartitionReceiveHandler.cs | 12 +- stylecop.json | 12 + stylecop.props | 8 + stylecop.ruleset | 36 + 761 files changed, 19568 insertions(+), 15136 deletions(-) create mode 100644 Azure.IoT.Edge.DotSettings create mode 100644 stylecop.json create mode 100644 stylecop.props create mode 100644 stylecop.ruleset diff --git a/.gitignore b/.gitignore index e6f75732cb1..8f6d08c7d32 100644 --- a/.gitignore +++ b/.gitignore @@ -244,7 +244,6 @@ AppPackages/ # Others *.[Cc]ache ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl diff --git a/Azure.IoT.Edge.DotSettings b/Azure.IoT.Edge.DotSettings new file mode 100644 index 00000000000..84db52c2e8e --- /dev/null +++ b/Azure.IoT.Edge.DotSettings @@ -0,0 +1,853 @@ + + False + False + + CSharp70 + + True + False + True + False + True + False + True + 2B10082F-01D6-4708-AA67-0D1C00414A12/d:generatedCode + 48F30397-9D10-4305-8E9D-35F7AE4ACE7C/d:workloadtestserver/d:Controllers/f:Controller.cs + 77CF0F04-0DE0-4D81-93DE-56006D7BE213/d:TestServer/d:Controllers/f:EdgeletTestController.cs + B0F37919-0D16-490F-B7A1-665BB93B9A62/d:edged/d:generatedCode + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + + True + HINT + HINT + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + HINT + HINT + SUGGESTION + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + DO_NOT_SHOW + HINT + SUGGESTION + HINT + HINT + HINT + DO_NOT_SHOW + HINT + HINT + DO_NOT_SHOW + HINT + HINT + SUGGESTION + HINT + DO_NOT_SHOW + HINT + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + SUGGESTION + HINT + <?xml version="1.0" encoding="utf-16"?><Profile name="Azure.IoT.Edge"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><HtmlReformatCode>True</HtmlReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUpdateFileHeader>True</CSUpdateFileHeader><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><VBReformatCode>True</VBReformatCode><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssAlphabetizeProperties>True</CssAlphabetizeProperties><CssReformatCode>True</CssReformatCode><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>IMPLICIT_EXCEPT_SIMPLE_TYPES</LocalVariableStyle><ForeachVariableStyle>IMPLICIT_EXCEPT_SIMPLE_TYPES</ForeachVariableStyle></CSUseVar><XMLReformatCode>True</XMLReformatCode><StyleCop.Documentation><SA1600ElementsMustBeDocumented>False</SA1600ElementsMustBeDocumented><SA1604ElementDocumentationMustHaveSummary>False</SA1604ElementDocumentationMustHaveSummary><SA1609PropertyDocumentationMustHaveValueDocumented>False</SA1609PropertyDocumentationMustHaveValueDocumented><SA1611ElementParametersMustBeDocumented>False</SA1611ElementParametersMustBeDocumented><SA1615ElementReturnValueMustBeDocumented>True</SA1615ElementReturnValueMustBeDocumented><SA1617VoidReturnValueMustNotBeDocumented>True</SA1617VoidReturnValueMustNotBeDocumented><SA1618GenericTypeParametersMustBeDocumented>False</SA1618GenericTypeParametersMustBeDocumented><SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes>True</SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes><SA1628DocumentationTextMustBeginWithACapitalLetter>True</SA1628DocumentationTextMustBeginWithACapitalLetter><SA1629DocumentationTextMustEndWithAPeriod>True</SA1629DocumentationTextMustEndWithAPeriod><SA1633SA1641UpdateFileHeader>Ignore</SA1633SA1641UpdateFileHeader><SA1639FileHeaderMustHaveSummary>False</SA1639FileHeaderMustHaveSummary><SA1642ConstructorSummaryDocumentationMustBeginWithStandardText>True</SA1642ConstructorSummaryDocumentationMustBeginWithStandardText><SA1643DestructorSummaryDocumentationMustBeginWithStandardText>True</SA1643DestructorSummaryDocumentationMustBeginWithStandardText><SA1644DocumentationHeadersMustNotContainBlankLines>True</SA1644DocumentationHeadersMustNotContainBlankLines></StyleCop.Documentation><CSReorderTypeMembers>True</CSReorderTypeMembers><CSArrangeQualifiers>True</CSArrangeQualifiers><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><OptimizeImportsTs>True</OptimizeImportsTs><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="True" ArrangeAttributes="True" /></Profile> + + + True + <?xml version="1.0" encoding="utf-16"?><Profile name="StyleCop"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><StyleCop.Layout><SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine>True</SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine><SA1509OpeningCurlyBracketsMustNotBePrecededByBlankLine>True</SA1509OpeningCurlyBracketsMustNotBePrecededByBlankLine><SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine>True</SA1510ChainedStatementBlocksMustNotBePrecededByBlankLine><SA1511WhileDoFooterMustNotBePrecededByBlankLine>True</SA1511WhileDoFooterMustNotBePrecededByBlankLine><SA1512SingleLineCommentsMustNotBeFollowedByBlankLine>True</SA1512SingleLineCommentsMustNotBeFollowedByBlankLine><SA1513ClosingCurlyBracketMustBeFollowedByBlankLine>True</SA1513ClosingCurlyBracketMustBeFollowedByBlankLine><SA1514ElementDocumentationHeaderMustBePrecededByBlankLine>True</SA1514ElementDocumentationHeaderMustBePrecededByBlankLine><SA1515SingleLineCommentMustBeProceededByBlankLine>True</SA1515SingleLineCommentMustBeProceededByBlankLine></StyleCop.Layout><StyleCop.Maintainability><SA1119StatementMustNotUseUnnecessaryParenthesis>True</SA1119StatementMustNotUseUnnecessaryParenthesis></StyleCop.Maintainability><StyleCop.Ordering><AlphabeticalUsingDirectives>Alphabetical</AlphabeticalUsingDirectives><ExpandUsingDirectives>FullyQualify</ExpandUsingDirectives><SA1212PropertyAccessorsMustFollowOrder>True</SA1212PropertyAccessorsMustFollowOrder><SA1213EventAccessorsMustFollowOrder>True</SA1213EventAccessorsMustFollowOrder></StyleCop.Ordering><StyleCop.Readability><SA1100DoNotPrefixCallsWithBaseUnlessLocalImplementationExists>True</SA1100DoNotPrefixCallsWithBaseUnlessLocalImplementationExists><SA1106CodeMustNotContainEmptyStatements>True</SA1106CodeMustNotContainEmptyStatements><SA1108BlockStatementsMustNotContainEmbeddedComments>True</SA1108BlockStatementsMustNotContainEmbeddedComments><SA1109BlockStatementsMustNotContainEmbeddedRegions>True</SA1109BlockStatementsMustNotContainEmbeddedRegions><SA1120CommentsMustContainText>True</SA1120CommentsMustContainText><SA1121UseBuiltInTypeAlias>True</SA1121UseBuiltInTypeAlias><SA1122UseStringEmptyForEmptyStrings>True</SA1122UseStringEmptyForEmptyStrings><SA1123DoNotPlaceRegionsWithinElements>True</SA1123DoNotPlaceRegionsWithinElements><SA1124CodeMustNotContainEmptyRegions>True</SA1124CodeMustNotContainEmptyRegions></StyleCop.Readability><StyleCop.Spacing><SA1001CommasMustBeSpacedCorrectly>True</SA1001CommasMustBeSpacedCorrectly><SA1005SingleLineCommentsMustBeginWithSingleSpace>True</SA1005SingleLineCommentsMustBeginWithSingleSpace><SA1006PreprocessorKeywordsMustNotBePrecededBySpace>True</SA1006PreprocessorKeywordsMustNotBePrecededBySpace><SA1021NegativeSignsMustBeSpacedCorrectly>True</SA1021NegativeSignsMustBeSpacedCorrectly><SA1022PositiveSignsMustBeSpacedCorrectly>True</SA1022PositiveSignsMustBeSpacedCorrectly><SA1025CodeMustNotContainMultipleWhitespaceInARow>True</SA1025CodeMustNotContainMultipleWhitespaceInARow></StyleCop.Spacing><StyleCop.Documentation><SA1600ElementsMustBeDocumented>True</SA1600ElementsMustBeDocumented><SA1604ElementDocumentationMustHaveSummary>True</SA1604ElementDocumentationMustHaveSummary><SA1609PropertyDocumentationMustHaveValueDocumented>True</SA1609PropertyDocumentationMustHaveValueDocumented><SA1611ElementParametersMustBeDocumented>True</SA1611ElementParametersMustBeDocumented><SA1615ElementReturnValueMustBeDocumented>True</SA1615ElementReturnValueMustBeDocumented><SA1617VoidReturnValueMustNotBeDocumented>True</SA1617VoidReturnValueMustNotBeDocumented><SA1618GenericTypeParametersMustBeDocumented>True</SA1618GenericTypeParametersMustBeDocumented><SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes>True</SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes><SA1628DocumentationTextMustBeginWithACapitalLetter>True</SA1628DocumentationTextMustBeginWithACapitalLetter><SA1629DocumentationTextMustEndWithAPeriod>True</SA1629DocumentationTextMustEndWithAPeriod><SA1633SA1641UpdateFileHeader>ReplaceCopyrightElement</SA1633SA1641UpdateFileHeader><SA1639FileHeaderMustHaveSummary>True</SA1639FileHeaderMustHaveSummary><SA1642ConstructorSummaryDocumentationMustBeginWithStandardText>True</SA1642ConstructorSummaryDocumentationMustBeginWithStandardText><SA1643DestructorSummaryDocumentationMustBeginWithStandardText>True</SA1643DestructorSummaryDocumentationMustBeginWithStandardText><SA1644DocumentationHeadersMustNotContainBlankLines>True</SA1644DocumentationHeadersMustNotContainBlankLines></StyleCop.Documentation></Profile> + Default + Azure.IoT.Edge + + False + Required + Required + Required + Required + Implicit + Implicit + public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async + ThisClass, BaseClass + All + True + True + False + NEXT_LINE + 0 + 1 + 1 + + 1 + 1 + + NEXT_LINE + SEPARATE + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + False + False + NEXT_LINE + 1 + 1 + True + public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async + NEVER + False + False + NEVER + True + ALWAYS + False + True + True + ALWAYS_USE + ON_SINGLE_LINE + True + True + False + True + False + True + True + False + True + False + True + True + CHOP_IF_LONG + True + True + CHOP_IF_LONG + 160 + False + CHOP_IF_LONG + True + True + DO_NOT_CHANGE + 1 + 1 + 1 + DO_NOT_CHANGE + False + DoNotTouch + False + False + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern Priority="150" DisplayName="Ignored Types"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </And> + <And> + <Or> + <Kind Is="Struct" /> + <Kind Is="Class" /> + </Or> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.StructLayoutAttribute" /> + <HasAttribute Name="System.Xml.Serialization.XmlTypeAttribute" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </And> + <And> + <Kind Is="Class" /> + <Name Is=".*NativeMethods" /> + </And> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern Priority="0" DisplayName="Default Pattern"> + <Entry DisplayName="Fields"> + <Entry.Match> + <Or> + <Kind Is="Field" /> + <Kind Is="Constant" /> + </Or> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Static /> + <Readonly /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors and Destructors" Priority="100"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <Kind Is="Destructor" /> + </Or> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Kind Order="Constructor Destructor" /> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Delegates"> + <Entry.Match> + <Kind Is="Delegate" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Events"> + <Entry.Match> + <Kind Is="Event" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Static /> + <ImplementsInterface Immediate="True" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Enums"> + <Entry.Match> + <Kind Is="Enum" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interfaces"> + <Entry.Match> + <Kind Is="Interface" /> + </Entry.Match> + <Entry.SortBy> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Explicit Interface Properties"> + <Entry.Match> + <And> + <Kind Is="Property" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Properties"> + <Entry.Match> + <Kind Is="Property" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Indexers" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Indexer" /> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Explicit Interface Indexers" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Indexer" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Other Indexers" Priority="100"> + <Entry.Match> + <Kind Is="Indexer" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Methods and Operators"> + <Entry.Match> + <And> + <Or> + <Kind Is="Method" /> + <Kind Is="Operator" /> + </Or> + <Access Is="Public" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Explicit Interface Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Non-Public Methods"> + <Entry.Match> + <Kind Is="Method" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Nested Structs" Priority="100"> + <Entry.Match> + <Kind Is="Struct" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + <Access Order="Public Internal ProtectedInternal Protected Private" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Nested Classes" Priority="100"> + <Entry.Match> + <Kind Is="Class" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Name /> + <Static /> + </Entry.SortBy> + </Entry> + </TypePattern> +</Patterns> + <?xml version="1.0" encoding="utf-8"?> +<Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> + + <!-- Do not reorder COM interfaces --> + <Pattern> + <Match> + <And Weight="2000"> + <Kind Is="interface"/> + <Or> + <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> + <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> + </Or> + </And> + </Match> + </Pattern> + + <!-- Do not reorder P/Invoke structs or XmlTypes --> + <Pattern> + <Match> + <And Weight="2000"> + <Or> + <Kind Is="struct"/> + <Kind Is="class"/> + </Or> + <Or> + <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> + <HasAttribute CLRName="System.Xml.Serialization.XmlTypeAttribute"/> + </Or> + </And> + </Match> + </Pattern> + + <!-- Do not reorder P/Invoke classes (called xxxNativeMethods) --> + <Pattern> + <Match> + <And Weight="2000"> + <Kind Is="class"/> + <Name Is=".*NativeMethods" /> + </And> + </Match> + </Pattern> + + <!-- StyleCop-Compatible Pattern --> + <Pattern RemoveAllRegions="true"> + <Match> + <Or Weight="1000" > + <Kind Is="class" /> + <Kind Is="struct" /> + <Kind Is="interface"/> + </Or> + </Match> + + <!-- Constants --> + <Entry> + <Match> + <Kind Is="constant"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Name/> + </Sort> + </Entry> + + <!-- Static Fields --> + <Entry> + <Match> + <And> + <Kind Is="field"/> + <Static/> + </And> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Readonly/> + <Name/> + </Sort> + </Entry> + + <!-- Instance Fields --> + <Entry> + <Match> + <And> + <Kind Is="field"/> + <Not> + <Static/> + </Not> + </And> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Readonly/> + <Name/> + </Sort> + </Entry> + + <!-- Constructors and Destructors --> + <Entry> + <Match> + <Or Weight="200"> + <Kind Is="constructor"/> + <Kind Is="destructor"/> + </Or> + </Match> + <Sort> + <Static/> + <Kind Order="constructor destructor"/> + <Access Order="public internal protected-internal protected private"/> + </Sort> + </Entry> + + <!-- Delegates --> + <Entry> + <Match> + <Kind Is="delegate"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static /> + <Name/> + </Sort> + </Entry> + + <!-- Public Events --> + <Entry> + <Match> + <And> + <Kind Is="event"/> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public" /> + <Static /> + <Name/> + </Sort> + </Entry> + + <!-- Explicit Interface Events --> + <Entry> + <Match> + <And> + <Kind Is="event"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + </Entry> + + <!-- Events --> + <Entry> + <Match> + <Kind Is="event"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static /> + <Name/> + </Sort> + </Entry> + + <!-- Enums --> + <Entry> + <Match> + <Kind Is="enum"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + </Entry> + + <!-- Interfaces --> + <Entry> + <Match> + <Kind Is="interface" /> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + </Entry> + + <!-- Public Properties --> + <Entry> + <Match> + <And> + <Kind Is="property"/> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public"/> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Explicit Interface Properties --> + <Entry> + <Match> + <And> + <Kind Is="property"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + </Entry> + + <!-- Properties --> + <Entry> + <Match> + <Kind Is="property"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Public Indexers --> + <Entry> + <Match> + <And> + <Kind Is="indexer" Weight="1000" /> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public" /> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Explicit Interface Indexers --> + <Entry> + <Match> + <And> + <Kind Is="indexer" Weight="1000"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + </Entry> + + <!-- Indexers --> + <Entry> + <Match> + <Kind Is="indexer" Weight="1000" /> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Public Methods and Operators --> + <Entry> + <Match> + <And> + <Or> + <Kind Is="method"/> + <Kind Is="operator"/> + </Or> + <Access Is="public"/> + </And> + </Match> + <Sort> + <Access Order="public"/> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Explicit Interface Methods --> + <Entry> + <Match> + <And> + <Kind Is="method"/> + <ImplementsInterface/> + </And> + </Match> + <Sort> + <ImplementsInterface Immediate="true"/> + <Name/> + </Sort> + </Entry> + + <!-- Methods --> + <Entry> + <Match> + <Kind Is="method"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private"/> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Operators --> + <Entry> + <Match> + <Kind Is="operator"/> + </Match> + <Sort> + <Access Order="public internal protected-internal protected private" /> + <Static/> + <Name/> + </Sort> + </Entry> + + <!-- Nested Structs --> + <Entry> + <Match> + <Kind Is="struct" + Weight="600" /> + </Match> + <Sort> + <Static /> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + </Entry> + + <!-- Nested Classes --> + <Entry> + <Match> + <Kind Is="class" + Weight="700" /> + </Match> + <Sort> + <Static /> + <Access Order="public internal protected-internal protected private" /> + <Name/> + </Sort> + </Entry> + + <!-- All Other Members --> + <Entry/> + + </Pattern> +</Patterns> + CustomLayout + True + + + + + True + + True + + True + True + + + + + + + + + UseExplicitType + UseVarWhenEvident + UseExplicitType + True + Copyright (c) Microsoft. All rights reserved. + True + True + True + True + True + True + False + True + True + False + True + True + False + True + True + False + False + True + False + True + IO + IP + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private, Internal" Description="Internal Class With Underscores"><ElementKinds><Kind Name="CLASS" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy></Policy> + Ensure.ArgumentNotNull({0}, "{0}"); + Ensure.ArgumentNotNull({0}, "{0}"); + True + True + True + True + $object$_On$event$ + $object$_On$event$ + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + + False + False + False + True + \ No newline at end of file diff --git a/Microsoft.Azure.Devices.Edge.sln.DotSettings b/Microsoft.Azure.Devices.Edge.sln.DotSettings index fdab617e149..ac6a9fe1e5c 100644 --- a/Microsoft.Azure.Devices.Edge.sln.DotSettings +++ b/Microsoft.Azure.Devices.Edge.sln.DotSettings @@ -1,114 +1,15 @@  -True -True -WARNING -DO_NOT_SHOW -HINT -SUGGESTION -HINT -DO_NOT_SHOW -DO_NOT_SHOW -HINT -DO_NOT_SHOW -HINT -DO_NOT_SHOW -DO_NOT_SHOW -DO_NOT_SHOW -DO_NOT_SHOW -DO_NOT_SHOW -HINT -DO_NOT_SHOW -HINT -WARNING -WARNING -WARNING -ERROR -HINT -<?xml version="1.0" encoding="utf-16"?><Profile name="Simple"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>IMPLICIT_WHEN_INITIALIZER_HAS_TYPE</LocalVariableStyle><ForeachVariableStyle>IMPLICIT_EXCEPT_PRIMITIVE_TYPES</ForeachVariableStyle></CSUseVar><CSUpdateFileHeader>True</CSUpdateFileHeader><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><StyleCop.Documentation><SA1600ElementsMustBeDocumented>False</SA1600ElementsMustBeDocumented><SA1604ElementDocumentationMustHaveSummary>False</SA1604ElementDocumentationMustHaveSummary><SA1609PropertyDocumentationMustHaveValueDocumented>False</SA1609PropertyDocumentationMustHaveValueDocumented><SA1611ElementParametersMustBeDocumented>False</SA1611ElementParametersMustBeDocumented><SA1615ElementReturnValueMustBeDocumented>False</SA1615ElementReturnValueMustBeDocumented><SA1617VoidReturnValueMustNotBeDocumented>False</SA1617VoidReturnValueMustNotBeDocumented><SA1618GenericTypeParametersMustBeDocumented>False</SA1618GenericTypeParametersMustBeDocumented><SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes>False</SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes><SA1628DocumentationTextMustBeginWithACapitalLetter>False</SA1628DocumentationTextMustBeginWithACapitalLetter><SA1629DocumentationTextMustEndWithAPeriod>False</SA1629DocumentationTextMustEndWithAPeriod><SA1633SA1641UpdateFileHeader>ReplaceAll</SA1633SA1641UpdateFileHeader><SA1639FileHeaderMustHaveSummary>False</SA1639FileHeaderMustHaveSummary><SA1642ConstructorSummaryDocumentationMustBeginWithStandardText>False</SA1642ConstructorSummaryDocumentationMustBeginWithStandardText><SA1643DestructorSummaryDocumentationMustBeginWithStandardText>False</SA1643DestructorSummaryDocumentationMustBeginWithStandardText><SA1644DocumentationHeadersMustNotContainBlankLines>False</SA1644DocumentationHeadersMustNotContainBlankLines></StyleCop.Documentation><CSShortenReferences>True</CSShortenReferences><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSharpFormatDocComments>True</CSharpFormatDocComments><CSArrangeTypeModifiers>True</CSArrangeTypeModifiers><CSArrangeTypeMemberModifiers>True</CSArrangeTypeMemberModifiers><CSSortModifiers>True</CSSortModifiers><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="True" ArrangeAttributes="False" /></Profile> -Simple -Simple -Implicit -Implicit -All -False -1 -1 -1 -False -False -ALWAYS_ADD -ALWAYS_ADD -ALWAYS_ADD -ALWAYS_ADD -1 -1 -public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async - NEVER -False -False - NEVER - NEVER -False -True -ALWAYS_USE -LINE_BREAK -False -True -False -False -True -False -True -True -CHOP_IF_LONG -True -True -CHOP_IF_LONG -140 -False -CHOP_ALWAYS -CHOP_ALWAYS -CHOP_IF_LONG -True -True -UseExplicitType -UseVarWhenEvident -UseVarWhenEvident -Copyright (c) Microsoft. All rights reserved. - -GC - IO -$object$_On$event$ -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> -<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - True - C:\Code\azureiot\Azure-IoT-Edge-Core\Microsoft.Azure.Devices.Edge.sln.DotSettings - - True - 1 + C:\git\iotedge\Azure.IoT.Edge.DotSettings + ..\Azure.IoT.Edge.DotSettings + True + True + 1 True True True True -True + True True -True -True - <data /> - <data><IncludeFilters /><ExcludeFilters /></data> - True - True + True + True + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Agent.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Agent.cs index 77e3a2163f4..bdda9f3a3bf 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Agent.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Agent.cs @@ -17,7 +17,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class Agent { const string StoreConfigKey = "CurrentConfig"; - IEnvironment environment; readonly IPlanner planner; readonly IPlanRunner planRunner; readonly IReporter reporter; @@ -28,12 +27,18 @@ public class Agent readonly AsyncLock reconcileLock = new AsyncLock(); readonly ISerde deploymentConfigInfoSerde; readonly IEncryptionProvider encryptionProvider; + IEnvironment environment; DeploymentConfigInfo currentConfig; - public Agent(IConfigSource configSource, IEnvironmentProvider environmentProvider, - IPlanner planner, IPlanRunner planRunner, IReporter reporter, + public Agent( + IConfigSource configSource, + IEnvironmentProvider environmentProvider, + IPlanner planner, + IPlanRunner planRunner, + IReporter reporter, IModuleIdentityLifecycleManager moduleIdentityLifecycleManager, - IEntityStore configStore, DeploymentConfigInfo initialDeployedConfigInfo, + IEntityStore configStore, + DeploymentConfigInfo initialDeployedConfigInfo, ISerde deploymentConfigInfoSerde, IEncryptionProvider encryptionProvider) { @@ -51,9 +56,16 @@ public Agent(IConfigSource configSource, IEnvironmentProvider environmentProvide Events.AgentCreated(); } - public static async Task Create(IConfigSource configSource, IPlanner planner, IPlanRunner planRunner, IReporter reporter, - IModuleIdentityLifecycleManager moduleIdentityLifecycleManager, IEnvironmentProvider environmentProvider, - IEntityStore configStore, ISerde deploymentConfigInfoSerde, IEncryptionProvider encryptionProvider) + public static async Task Create( + IConfigSource configSource, + IPlanner planner, + IPlanRunner planRunner, + IReporter reporter, + IModuleIdentityLifecycleManager moduleIdentityLifecycleManager, + IEnvironmentProvider environmentProvider, + IEntityStore configStore, + ISerde deploymentConfigInfoSerde, + IEncryptionProvider encryptionProvider) { Preconditions.CheckNotNull(deploymentConfigInfoSerde, nameof(deploymentConfigInfoSerde)); Preconditions.CheckNotNull(configStore, nameof(configStore)); @@ -62,85 +74,30 @@ public static async Task Create(IConfigSource configSource, IPlanner plan try { Option deploymentConfigInfoJson = await Preconditions.CheckNotNull(configStore, nameof(configStore)).Get(StoreConfigKey); - await deploymentConfigInfoJson.ForEachAsync(async json => - { - string decryptedJson = await encryptionProvider.DecryptAsync(json); - deploymentConfigInfo = Option.Some(deploymentConfigInfoSerde.Deserialize(decryptedJson)); - }); + await deploymentConfigInfoJson.ForEachAsync( + async json => + { + string decryptedJson = await encryptionProvider.DecryptAsync(json); + deploymentConfigInfo = Option.Some(deploymentConfigInfoSerde.Deserialize(decryptedJson)); + }); } catch (Exception ex) when (!ex.IsFatal()) { Events.ErrorDeserializingConfig(ex); } - var agent = new Agent(configSource, environmentProvider, planner, planRunner, reporter, moduleIdentityLifecycleManager, - configStore, deploymentConfigInfo.GetOrElse(DeploymentConfigInfo.Empty), deploymentConfigInfoSerde, encryptionProvider); - return agent; - } - - async Task<(ModuleSet current, Exception ex)> GetCurrentModuleSetAsync(CancellationToken token) - { - ModuleSet current = null; - Exception ex = null; - - try - { - current = await this.environment.GetModulesAsync(token); - } - catch (Exception e) when (!e.IsFatal()) - { - ex = e; - } - return (current, ex); - } - - async Task<(DeploymentConfigInfo deploymentConfigInfo, Exception ex)> GetDeploymentConfigInfoAsync() - { - DeploymentConfigInfo deploymentConfigInfo = null; - Exception ex = null; - - try - { - Events.GettingDeploymentConfigInfo(); - deploymentConfigInfo = await this.configSource.GetDeploymentConfigInfoAsync(); - Events.ObtainedDeploymentConfigInfo(deploymentConfigInfo); - } - catch (Exception e) when (!e.IsFatal()) - { - ex = e; - } - - return (deploymentConfigInfo, ex); - } - - async Task<(ModuleSet current, DeploymentConfigInfo DeploymentConfigInfo, Exception ex)> GetReconcileData(CancellationToken token) - { - // we read the data from the config source and from the environment separately because - // when doing something like TaskEx.WhenAll(t1, t2) if either of them throws then we get - // nothing; so for example, if the environment is able to successfully retrieve the moduleset - // but there's a corrupt deployment in IoT Hub then we end up not being able to report the - // current state even though we have it - - ((ModuleSet current, Exception environmentException), (DeploymentConfigInfo deploymentConfigInfo, Exception configSourceException)) = await TaskEx.WhenAll( - this.GetCurrentModuleSetAsync(token), this.GetDeploymentConfigInfoAsync() - ); - - List exceptions = new[] - { - environmentException, - configSourceException, - deploymentConfigInfo?.Exception.OrDefault() - } - .Where(e => e != null) - .ToList(); - - Exception exception = null; - if (exceptions.Any()) - { - exception = exceptions.Count > 1 ? new AggregateException(exceptions) : exceptions[0]; - } - - return (current, deploymentConfigInfo, exception); + var agent = new Agent( + configSource, + environmentProvider, + planner, + planRunner, + reporter, + moduleIdentityLifecycleManager, + configStore, + deploymentConfigInfo.GetOrElse(DeploymentConfigInfo.Empty), + deploymentConfigInfoSerde, + encryptionProvider); + return agent; } public async Task ReconcileAsync(CancellationToken token) @@ -165,7 +122,7 @@ public async Task ReconcileAsync(CancellationToken token) // TODO - Update this logic to create identities only when needed, in the Command factory, instead of creating all the identities // up front here. That will allow handling the case when only the state of the system has changed (say one module crashes), and // no new identities need to be created. This will simplify the logic to allow EdgeAgent to work when offline. - // But that required ModuleSet.Diff to be updated to include modules updated by deployment, and modules updated by state change. + // But that required ModuleSet.Diff to be updated to include modules updated by deployment, and modules updated by state change. IImmutableDictionary identities = await this.moduleIdentityLifecycleManager.GetModuleIdentitiesAsync(desiredModuleSet, current); Plan plan = await this.planner.PlanAsync(desiredModuleSet, current, deploymentConfig.Runtime, identities); if (!plan.IsEmpty) @@ -217,20 +174,11 @@ public async Task ReconcileAsync(CancellationToken token) break; } } + await this.reporter.ReportAsync(token, moduleSetToReport, await this.environment.GetRuntimeInfoAsync(), this.currentConfig.Version, status); } } - // This should be called only within the reconcile lock. - async Task UpdateCurrentConfig(DeploymentConfigInfo deploymentConfigInfo) - { - this.environment = this.environmentProvider.Create(deploymentConfigInfo.DeploymentConfig); - this.currentConfig = deploymentConfigInfo; - - string encryptedConfig = await this.encryptionProvider.EncryptAsync(this.deploymentConfigInfoSerde.Serialize(deploymentConfigInfo)); - await this.configStore.Put(StoreConfigKey, encryptedConfig); - } - public async Task HandleShutdown(CancellationToken token) { try @@ -248,6 +196,95 @@ public async Task HandleShutdown(CancellationToken token) } } + internal async Task ReportShutdownAsync(CancellationToken token) + { + try + { + var status = new DeploymentStatus(DeploymentStatusCode.Unknown, "Agent is not running"); + await this.reporter.ReportShutdown(status, token); + Events.ReportShutdown(); + } + catch (Exception ex) when (!ex.IsFatal()) + { + Events.ReportShutdownFailed(ex); + } + } + + async Task<(ModuleSet current, Exception ex)> GetCurrentModuleSetAsync(CancellationToken token) + { + ModuleSet current = null; + Exception ex = null; + + try + { + current = await this.environment.GetModulesAsync(token); + } + catch (Exception e) when (!e.IsFatal()) + { + ex = e; + } + + return (current, ex); + } + + async Task<(DeploymentConfigInfo deploymentConfigInfo, Exception ex)> GetDeploymentConfigInfoAsync() + { + DeploymentConfigInfo deploymentConfigInfo = null; + Exception ex = null; + + try + { + Events.GettingDeploymentConfigInfo(); + deploymentConfigInfo = await this.configSource.GetDeploymentConfigInfoAsync(); + Events.ObtainedDeploymentConfigInfo(deploymentConfigInfo); + } + catch (Exception e) when (!e.IsFatal()) + { + ex = e; + } + + return (deploymentConfigInfo, ex); + } + + async Task<(ModuleSet current, DeploymentConfigInfo DeploymentConfigInfo, Exception ex)> GetReconcileData(CancellationToken token) + { + // we read the data from the config source and from the environment separately because + // when doing something like TaskEx.WhenAll(t1, t2) if either of them throws then we get + // nothing; so for example, if the environment is able to successfully retrieve the moduleset + // but there's a corrupt deployment in IoT Hub then we end up not being able to report the + // current state even though we have it + ((ModuleSet current, Exception environmentException), (DeploymentConfigInfo deploymentConfigInfo, Exception configSourceException)) = await TaskEx.WhenAll( + this.GetCurrentModuleSetAsync(token), + this.GetDeploymentConfigInfoAsync()); + + List exceptions = new[] + { + environmentException, + configSourceException, + deploymentConfigInfo?.Exception.OrDefault() + } + .Where(e => e != null) + .ToList(); + + Exception exception = null; + if (exceptions.Any()) + { + exception = exceptions.Count > 1 ? new AggregateException(exceptions) : exceptions[0]; + } + + return (current, deploymentConfigInfo, exception); + } + + // This should be called only within the reconcile lock. + async Task UpdateCurrentConfig(DeploymentConfigInfo deploymentConfigInfo) + { + this.environment = this.environmentProvider.Create(deploymentConfigInfo.DeploymentConfig); + this.currentConfig = deploymentConfigInfo; + + string encryptedConfig = await this.encryptionProvider.EncryptAsync(this.deploymentConfigInfoSerde.Serialize(deploymentConfigInfo)); + await this.configStore.Put(StoreConfigKey, encryptedConfig); + } + async Task ShutdownModules(CancellationToken token) { try @@ -269,24 +306,10 @@ async Task ShutdownModules(CancellationToken token) } } - internal async Task ReportShutdownAsync(CancellationToken token) - { - try - { - var status = new DeploymentStatus(DeploymentStatusCode.Unknown, "Agent is not running"); - await this.reporter.ReportShutdown(status, token); - Events.ReportShutdown(); - } - catch (Exception ex) when (!ex.IsFatal()) - { - Events.ReportShutdownFailed(ex); - } - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.Agent; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -366,11 +389,6 @@ public static void ObtainedDeploymentConfigInfo(DeploymentConfigInfo deploymentC } } - internal static void ErrorDeserializingConfig(Exception ex) - { - Log.LogWarning((int)EventIds.ErrorDeserializingConfig, ex, "There was an error deserializing stored deployment configuration information"); - } - public static void InitiateShutdown() { Log.LogInformation((int)EventIds.InitiateShutdown, "Initiating shutdown cleanup."); @@ -395,6 +413,11 @@ public static void ShutdownModulesFailed(Exception ex) { Log.LogWarning((int)EventIds.StopModulesFailed, ex, "Error while stopping all modules."); } + + internal static void ErrorDeserializingConfig(Exception ex) + { + Log.LogWarning((int)EventIds.ErrorDeserializingConfig, ex, "There was an error deserializing stored deployment configuration information"); + } } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/AgentEventIds.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/AgentEventIds.cs index 8d2b0af64a8..4d6edeb3a87 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/AgentEventIds.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/AgentEventIds.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core { public struct AgentEventIds { - const int EventIdStart = 100000; public const int Agent = EventIdStart; public const int FileConfigSource = EventIdStart + 100; public const int TwinConfigSource = EventIdStart + 200; @@ -21,5 +20,6 @@ public struct AgentEventIds public const int OrderedRetryPlanRunner = EventIdStart + 1400; public const int ModuleManagementHttpClient = EventIdStart + 1500; public const int ModuleIdentityLifecycleManager = EventIdStart + 1600; + const int EventIdStart = 100000; } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfig.cs index d42bd80f09a..2eef9b6e6b0 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfig.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfig.cs @@ -9,6 +9,12 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class DeploymentConfig : IEquatable { + public static DeploymentConfig Empty = new DeploymentConfig( + "1.0", + UnknownRuntimeInfo.Instance, + new SystemModules(UnknownEdgeAgentModule.Instance, UnknownEdgeHubModule.Instance), + ImmutableDictionary.Empty); + static readonly ReadOnlyDictionaryComparer ModuleDictionaryComparer = new ReadOnlyDictionaryComparer(); [JsonConstructor] @@ -25,20 +31,6 @@ public DeploymentConfig( this.UpdateModuleNames(); } - void UpdateModuleNames() - { - foreach (KeyValuePair module in this.Modules) - { - module.Value.Name = module.Key; - } - } - - public static DeploymentConfig Empty = new DeploymentConfig( - "1.0", - UnknownRuntimeInfo.Instance, - new SystemModules(UnknownEdgeAgentModule.Instance, UnknownEdgeHubModule.Instance), - ImmutableDictionary.Empty); - [JsonProperty("schemaVersion")] public string SchemaVersion { get; } @@ -69,13 +61,19 @@ public ModuleSet GetModuleSet() public bool Equals(DeploymentConfig other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return string.Equals(this.SchemaVersion, other.SchemaVersion) - && Equals(this.Runtime, other.Runtime) - && Equals(this.SystemModules, other.SystemModules) - && ModuleDictionaryComparer.Equals(this.Modules, other.Modules); + && Equals(this.Runtime, other.Runtime) + && Equals(this.SystemModules, other.SystemModules) + && ModuleDictionaryComparer.Equals(this.Modules, other.Modules); } public override bool Equals(object obj) @@ -93,12 +91,20 @@ public override int GetHashCode() { unchecked { - int hashCode = (this.SchemaVersion != null ? this.SchemaVersion.GetHashCode() : 0); + int hashCode = this.SchemaVersion != null ? this.SchemaVersion.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (this.Runtime != null ? this.Runtime.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.SystemModules != null ? this.SystemModules.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.Modules != null ? this.Modules.GetHashCode() : 0); return hashCode; } } - } + + void UpdateModuleNames() + { + foreach (KeyValuePair module in this.Modules) + { + module.Value.Name = module.Key; + } + } + } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfigInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfigInfo.cs index 102a8eac9ff..211509e201a 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfigInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentConfigInfo.cs @@ -33,6 +33,10 @@ public DeploymentConfigInfo(long version, Exception ex) [JsonIgnore] public Option Exception { get; } + public static bool operator ==(DeploymentConfigInfo left, DeploymentConfigInfo right) => Equals(left, right); + + public static bool operator !=(DeploymentConfigInfo left, DeploymentConfigInfo right) => !Equals(left, right); + public bool Equals(DeploymentConfigInfo other) { if (other is null) @@ -46,7 +50,7 @@ public bool Equals(DeploymentConfigInfo other) } return this.Version == other.Version && Equals(this.DeploymentConfig, other.DeploymentConfig) - && this.Exception.Equals(other.Exception); + && this.Exception.Equals(other.Exception); } public override bool Equals(object obj) @@ -79,9 +83,5 @@ public override int GetHashCode() return hashCode; } } - - public static bool operator ==(DeploymentConfigInfo left, DeploymentConfigInfo right) => Equals(left, right); - - public static bool operator !=(DeploymentConfigInfo left, DeploymentConfigInfo right) => !Equals(left, right); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentStatus.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentStatus.cs index f9b8ce17e9f..988eccf2725 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentStatus.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/DeploymentStatus.cs @@ -10,8 +10,8 @@ public class DeploymentStatus : IEquatable public static readonly DeploymentStatus Unknown = new DeploymentStatus(DeploymentStatusCode.Unknown); public static readonly DeploymentStatus Success = new DeploymentStatus(DeploymentStatusCode.Successful); - public DeploymentStatus(DeploymentStatusCode code): - this(code, string.Empty) + public DeploymentStatus(DeploymentStatusCode code) + : this(code, string.Empty) { } @@ -28,6 +28,16 @@ public DeploymentStatus(DeploymentStatusCode code, string description) [JsonProperty(PropertyName = "description")] public string Description { get; } + public static bool operator ==(DeploymentStatus status1, DeploymentStatus status2) + { + return EqualityComparer.Default.Equals(status1, status2); + } + + public static bool operator !=(DeploymentStatus status1, DeploymentStatus status2) + { + return !(status1 == status2); + } + public override bool Equals(object obj) => this.Equals(obj as DeploymentStatus); public bool Equals(DeploymentStatus other) @@ -45,16 +55,6 @@ public override int GetHashCode() return hashCode; } - public static bool operator ==(DeploymentStatus status1, DeploymentStatus status2) - { - return EqualityComparer.Default.Equals(status1, status2); - } - - public static bool operator !=(DeploymentStatus status1, DeploymentStatus status2) - { - return !(status1 == status2); - } - public DeploymentStatus Clone() => new DeploymentStatus(this.Code, this.Description); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Diff.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Diff.cs index 22b60bffabd..93ef260704b 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Diff.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Diff.cs @@ -8,6 +8,12 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class Diff { + public Diff(IList updated, IList removed) + { + this.Updated = Preconditions.CheckNotNull(updated, nameof(updated)).ToImmutableHashSet(); + this.Removed = Preconditions.CheckNotNull(removed, nameof(removed)).ToImmutableHashSet(); + } + public static Diff Empty { get; } = new Diff(ImmutableList.Empty, ImmutableList.Empty); public bool IsEmpty => this.Updated.Count == 0 && this.Removed.Count == 0; @@ -43,19 +49,8 @@ public class Diff /// public IImmutableSet Removed { get; } - public Diff(IList updated, IList removed) - { - this.Updated = Preconditions.CheckNotNull(updated, nameof(updated)).ToImmutableHashSet(); - this.Removed = Preconditions.CheckNotNull(removed, nameof(removed)).ToImmutableHashSet(); - } - public static Diff Create(params IModule[] updated) => new Diff(updated.ToList(), ImmutableList.Empty); - protected bool Equals(Diff other) - { - return this.Updated.SetEquals(other.Updated) && this.Removed.SetEquals(other.Removed); - } - public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) @@ -75,5 +70,10 @@ public override int GetHashCode() return hash; } } + + protected bool Equals(Diff other) + { + return this.Updated.SetEquals(other.Updated) && this.Removed.SetEquals(other.Removed); + } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EmptyModuleSetException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EmptyModuleSetException.cs index a2d11e118ee..20c619101f5 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EmptyModuleSetException.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EmptyModuleSetException.cs @@ -6,11 +6,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core [Serializable] public class EmptyModuleSetException : Exception { - public EmptyModuleSetException(string message) : base(message) + public EmptyModuleSetException(string message) + : base(message) { } - public EmptyModuleSetException(string message, Exception inner) : base(message, inner) + public EmptyModuleSetException(string message, Exception inner) + : base(message, inner) { } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EnvVal.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EnvVal.cs index 758fe3bc88f..d836661e4ec 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EnvVal.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/EnvVal.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Agent.Core { - using Newtonsoft.Json; using System; using System.Collections.Generic; + using Newtonsoft.Json; public class EnvVal : IEquatable { @@ -21,9 +21,15 @@ public EnvVal(string value) public bool Equals(EnvVal other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return string.Equals(this.Value, other.Value); } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ICombinedConfigProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ICombinedConfigProvider.cs index 9c11c1b65bd..c0b23359fc0 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ICombinedConfigProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ICombinedConfigProvider.cs @@ -5,7 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core /// An implementation of this is expected to combine the type specific config info from the module and /// the runtime info objects and return it. /// For example, for Docker config, this implementation will combine docker image and docker create - /// options from the module, and the registry credentials from the runtime info and return them. + /// options from the module, and the registry credentials from the runtime info and return them. /// /// The type of the combined config object returned public interface ICombinedConfigProvider diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IConfigSource.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IConfigSource.cs index 25b70a9eb2d..1e8e97266d6 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IConfigSource.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IConfigSource.cs @@ -7,8 +7,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public interface IConfigSource : IDisposable { - Task GetDeploymentConfigInfoAsync(); - IConfiguration Configuration { get; } + + Task GetDeploymentConfigInfoAsync(); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IEnvironment.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IEnvironment.cs index 955f65733d5..51beae8021c 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IEnvironment.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IEnvironment.cs @@ -18,5 +18,5 @@ public interface IEnvironment /// An object that contains the runtime information. Task GetRuntimeInfoAsync(); - } + } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IModule.cs index 8c9a6e4b2a8..9387a237d51 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IModule.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core { using System; - using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization; using Newtonsoft.Json; @@ -54,7 +53,6 @@ public enum ModuleStatus /// The "Failed" state indicates that the module exited with a failure exit code /// (non-zer0). The module can transition back to "Backoff" from this state /// depending on the restart policy in effect. - /// /// This state can indicate that the module has experienced an unrecoverable error. /// This happens when the MMA has given up on trying to resuscitate the module and user /// action is required to update its code/configuration in order for it to work again @@ -98,7 +96,7 @@ public interface IModule : IEquatable ModuleStatus DesiredStatus { get; } [JsonProperty(PropertyName = "restartPolicy")] - RestartPolicy RestartPolicy { get; } + RestartPolicy RestartPolicy { get; } [JsonIgnore] ConfigurationInfo ConfigurationInfo { get; } @@ -111,7 +109,7 @@ public interface IModule : IModule, IEquatable> { [JsonProperty(PropertyName = "settings")] TConfig Config { get; } - } + } public interface IEdgeHubModule : IModule { @@ -119,5 +117,5 @@ public interface IEdgeHubModule : IModule public interface IEdgeAgentModule : IModule { - } + } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IPlanner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IPlanner.cs index cedd62fe383..4e3472eb09c 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IPlanner.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IPlanner.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Agent.Core { - using System.Threading.Tasks; - using System.Collections.Immutable; + using System.Threading.Tasks; /// /// Allows the deployment strategy to be abstracted. diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfo.cs index 2369d2896be..abc774e094b 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfo.cs @@ -15,13 +15,15 @@ public interface IRuntimeInfo : IRuntimeInfo public class UnknownRuntimeInfo : IRuntimeInfo { - UnknownRuntimeInfo() { } + UnknownRuntimeInfo() + { + } public static UnknownRuntimeInfo Instance { get; } = new UnknownRuntimeInfo(); public string Type => Constants.Unknown; public bool Equals(IRuntimeInfo other) => - other != null && object.ReferenceEquals(this, other); + other != null && ReferenceEquals(this, other); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfoProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfoProvider.cs index 753d8148c21..f4eb2604057 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfoProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeInfoProvider.cs @@ -20,8 +20,6 @@ public interface IRuntimeInfoProvider public class SystemInfo { - static SystemInfo Empty { get; } = new SystemInfo(string.Empty, string.Empty, string.Empty); - [JsonConstructor] public SystemInfo(string operatingSystemType, string architecture, string version) { @@ -35,5 +33,7 @@ public SystemInfo(string operatingSystemType, string architecture, string versio public string Architecture { get; } public string Version { get; } + + static SystemInfo Empty { get; } = new SystemInfo(string.Empty, string.Empty, string.Empty); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeStatusModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeStatusModule.cs index 357a109be66..fd509aa824c 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeStatusModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IRuntimeStatusModule.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Agent.Core { - public interface IRuntimeStatusModule + public interface IRuntimeStatusModule { IModule WithRuntimeStatus(ModuleStatus newStatus); } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IdentityProviderServiceCredentials.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IdentityProviderServiceCredentials.cs index 1e4dbddc2b4..99d2e4479d6 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IdentityProviderServiceCredentials.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/IdentityProviderServiceCredentials.cs @@ -7,7 +7,8 @@ public class IdentityProviderServiceCredentials : ICredentials { const string DefaultAuthScheme = "sasToken"; - public IdentityProviderServiceCredentials(string providerUri, string moduleGenerationId, string authScheme = DefaultAuthScheme) : this(providerUri, moduleGenerationId, authScheme, Option.None()) + public IdentityProviderServiceCredentials(string providerUri, string moduleGenerationId, string authScheme = DefaultAuthScheme) + : this(providerUri, moduleGenerationId, authScheme, Option.None()) { } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Microsoft.Azure.Devices.Edge.Agent.Core.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Microsoft.Azure.Devices.Edge.Agent.Core.csproj index 0854625358d..bbe1c030968 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Microsoft.Azure.Devices.Edge.Agent.Core.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Microsoft.Azure.Devices.Edge.Agent.Core.csproj @@ -33,4 +33,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleConnectionStringBuilder.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleConnectionStringBuilder.cs index 07b3c6e1233..5cf2e90a7c2 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleConnectionStringBuilder.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleConnectionStringBuilder.cs @@ -33,6 +33,7 @@ static void AppendIfNotEmpty(StringBuilder stringBuilder, string propertyName, s { stringBuilder.Append(ValuePairDelimiter); } + stringBuilder.Append($"{propertyName}={propertyValue}"); } } @@ -52,6 +53,8 @@ public ModuleConnectionString(string iotHubHostName, string deviceId, string mod this.moduleId = moduleId; } + public static implicit operator string(ModuleConnectionString moduleConnectionStringBuilder) => moduleConnectionStringBuilder.Build(); + public ModuleConnectionString WithGatewayHostName(string gatewayHostName) { this.gatewayHostName = Preconditions.CheckNonWhiteSpace(gatewayHostName, nameof(gatewayHostName)); @@ -79,8 +82,6 @@ public string Build() AppendIfNotEmpty(connectionString, GatewayHostNamePropertyName, this.gatewayHostName); return connectionString.ToString(); } - - public static implicit operator string(ModuleConnectionString moduleConnectionStringBuilder) => moduleConnectionStringBuilder.Build(); } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleIdentityHelper.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleIdentityHelper.cs index a390854186c..b59b767adad 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleIdentityHelper.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleIdentityHelper.cs @@ -7,7 +7,6 @@ public class ModuleIdentityHelper // and vice versa, to make sure the right values are being used. // TODO - This will fail if the user adds modules with the same module name as a system module - for example a module called // edgeHub. We might have to catch such cases and flag them as error (or handle them in some other way). - public static string GetModuleIdentityName(string moduleName) { if (moduleName.Equals(Constants.EdgeHubModuleName)) @@ -18,6 +17,7 @@ public static string GetModuleIdentityName(string moduleName) { return Constants.EdgeAgentModuleIdentityName; } + return moduleName; } @@ -31,6 +31,7 @@ public static string GetModuleName(string moduleIdentityName) { return Constants.EdgeAgentModuleName; } + return moduleIdentityName; } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleRuntimeInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleRuntimeInfo.cs index c870553c7a9..4362eecb339 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleRuntimeInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleRuntimeInfo.cs @@ -6,8 +6,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class ModuleRuntimeInfo { - public ModuleRuntimeInfo(string name, string type, ModuleStatus moduleStatus, string description, - long exitCode, Option startTime, Option exitTime) + public ModuleRuntimeInfo( + string name, + string type, + ModuleStatus moduleStatus, + string description, + long exitCode, + Option startTime, + Option exitTime) { this.Name = name; this.Type = type; @@ -35,8 +41,15 @@ public ModuleRuntimeInfo(string name, string type, ModuleStatus moduleStatus, st public class ModuleRuntimeInfo : ModuleRuntimeInfo { - public ModuleRuntimeInfo(string name, string type, ModuleStatus moduleStatus, string description, - long exitCode, Option startTime, Option exitTime, T config) + public ModuleRuntimeInfo( + string name, + string type, + ModuleStatus moduleStatus, + string description, + long exitCode, + Option startTime, + Option exitTime, + T config) : base(name, type, moduleStatus, description, exitCode, startTime, exitTime) { this.Config = config; diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleSet.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleSet.cs index 77355c307de..9ccaf440208 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleSet.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleSet.cs @@ -10,12 +10,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class ModuleSet : IEquatable { - public static ModuleSet Empty { get; } = new ModuleSet(ImmutableDictionary.Empty as IImmutableDictionary); - static readonly DictionaryComparer ModuleDictionaryComparer = new DictionaryComparer(); - public IImmutableDictionary Modules { get; } - [JsonConstructor] public ModuleSet(IDictionary modules) { @@ -27,8 +23,19 @@ public ModuleSet(IImmutableDictionary modules) this.Modules = modules ?? Empty.Modules; } + public static ModuleSet Empty { get; } = new ModuleSet(ImmutableDictionary.Empty as IImmutableDictionary); + + public IImmutableDictionary Modules { get; } + public static ModuleSet Create(params IModule[] modules) => new ModuleSet(modules.ToDictionary(m => m.Name, m => m)); + public static bool operator ==(ModuleSet set1, ModuleSet set2) => + ((object)set1 == null && (object)set2 == null) + || + ((object)set1 != null && set1.Equals(set2)); + + public static bool operator !=(ModuleSet set1, ModuleSet set2) => !(set1 == set2); + public bool TryGetModule(string key, out IModule module) => this.Modules.TryGetValue(key, out module); public ModuleSet ApplyDiff(Diff diff) @@ -74,15 +81,8 @@ public Diff Diff(ModuleSet other) public override bool Equals(object obj) => this.Equals(obj as ModuleSet); public bool Equals(ModuleSet other) => other != null && - ModuleDictionaryComparer.Equals(this.Modules.ToImmutableDictionary(), other.Modules.ToImmutableDictionary()); + ModuleDictionaryComparer.Equals(this.Modules.ToImmutableDictionary(), other.Modules.ToImmutableDictionary()); public override int GetHashCode() => 1729798618 + ModuleDictionaryComparer.GetHashCode(this.Modules.ToImmutableDictionary()); - - public static bool operator ==(ModuleSet set1, ModuleSet set2) => - ((object)set1 == null && (object)set2 == null) - || - ((object)set1 != null && set1.Equals(set2)); - - public static bool operator !=(ModuleSet set1, ModuleSet set2) => !(set1 == set2); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleState.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleState.cs index 99a0c705ec4..8f73c1dcaec 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleState.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleState.cs @@ -6,14 +6,15 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class ModuleState { - public int RestartCount { get; } - public DateTime LastRestartTimeUtc { get; } - [JsonConstructor] public ModuleState(int restartCount, DateTime lastRestartTimeUtc) { this.RestartCount = restartCount; this.LastRestartTimeUtc = lastRestartTimeUtc; } + + public int RestartCount { get; } + + public DateTime LastRestartTimeUtc { get; } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleWithIdentity.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleWithIdentity.cs index 652f6e770d1..5fcbc697027 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleWithIdentity.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/ModuleWithIdentity.cs @@ -20,13 +20,19 @@ public ModuleWithIdentity(IModule module, IModuleIdentity moduleIdentity) public IModuleIdentity ModuleIdentity => this.moduleIdentity; public override bool Equals(object obj) => this.Equals(obj as ModuleWithIdentity); - + public virtual bool Equals(IModuleWithIdentity other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return this.module.Equals(other.Module) && this.moduleIdentity.Equals(other.ModuleIdentity); } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/NullEnvironment.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/NullEnvironment.cs index da7c5f117d4..4b8c24b1859 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/NullEnvironment.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/NullEnvironment.cs @@ -6,16 +6,16 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class NullEnvironment : IEnvironment { + NullEnvironment() + { + } + public static NullEnvironment Instance { get; } = new NullEnvironment(); public string OperatingSystemType => string.Empty; public string Architecture => string.Empty; - NullEnvironment() - { - } - public Task GetModulesAsync(CancellationToken token) => Task.FromResult(ModuleSet.Empty); public Task GetEdgeAgentModuleAsync(CancellationToken token) => Task.FromResult((IEdgeAgentModule)null); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Plan.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Plan.cs index 33fec9e3edf..e773fa93029 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Plan.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/Plan.cs @@ -7,15 +7,15 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core public class Plan { - public static Plan Empty { get; } = new Plan(ImmutableList.Empty); - - public bool IsEmpty => this.Commands.IsEmpty; - public Plan(IList commands) { this.Commands = Preconditions.CheckNotNull(commands.ToImmutableList(), nameof(commands)); } + public static Plan Empty { get; } = new Plan(ImmutableList.Empty); + + public bool IsEmpty => this.Commands.IsEmpty; + public ImmutableList Commands { get; } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/RestartPolicyManager.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/RestartPolicyManager.cs index 818e67003fd..0098d0116ef 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/RestartPolicyManager.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/RestartPolicyManager.cs @@ -56,6 +56,12 @@ public ModuleStatus ComputeModuleStatusFromRestartPolicy(ModuleStatus status, Re return status; } + public IEnumerable ApplyRestartPolicy(IEnumerable modules) => + modules.Where(module => this.ShouldRestart(module)); + + internal TimeSpan GetCoolOffPeriod(int restartCount) => + TimeSpan.FromSeconds(Math.Min(this.coolOffTimeUnitInSeconds * Math.Pow(2, restartCount), MaxCoolOffPeriodSecs)); + bool ShouldRestart(IRuntimeModule module) { // we don't really know what status "Unknown" means @@ -82,18 +88,12 @@ bool ShouldRestart(IRuntimeModule module) return false; } - - internal TimeSpan GetCoolOffPeriod(int restartCount) => - TimeSpan.FromSeconds(Math.Min(this.coolOffTimeUnitInSeconds * Math.Pow(2, restartCount), MaxCoolOffPeriodSecs)); - - public IEnumerable ApplyRestartPolicy(IEnumerable modules) => - modules.Where(module => this.ShouldRestart(module)); } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.RestartManager; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/SystemModules.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/SystemModules.cs index e3e584290e5..0f835f9605f 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/SystemModules.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/SystemModules.cs @@ -30,13 +30,23 @@ public SystemModules(Option edgeAgent, Option [JsonConverter(typeof(OptionConverter))] public Option EdgeAgent { get; } + public static bool operator ==(SystemModules modules1, SystemModules modules2) + { + return EqualityComparer.Default.Equals(modules1, modules2); + } + + public static bool operator !=(SystemModules modules1, SystemModules modules2) + { + return !(modules1 == modules2); + } + public override bool Equals(object obj) => this.Equals(obj as SystemModules); public bool Equals(SystemModules other) { return other != null && - EqualityComparer>.Default.Equals(this.EdgeHub, other.EdgeHub) && - EqualityComparer>.Default.Equals(this.EdgeAgent, other.EdgeAgent); + EqualityComparer>.Default.Equals(this.EdgeHub, other.EdgeHub) && + EqualityComparer>.Default.Equals(this.EdgeAgent, other.EdgeAgent); } public override int GetHashCode() @@ -47,16 +57,6 @@ public override int GetHashCode() return hashCode; } - public static bool operator ==(SystemModules modules1, SystemModules modules2) - { - return EqualityComparer.Default.Equals(modules1, modules2); - } - - public static bool operator !=(SystemModules modules1, SystemModules modules2) - { - return !(modules1 == modules2); - } - public SystemModules Clone() => new SystemModules(this.EdgeAgent, this.EdgeHub); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/UnknownModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/UnknownModule.cs index eb6ea46454a..849e24f4818 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/UnknownModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/UnknownModule.cs @@ -9,7 +9,11 @@ public class UnknownModule : IModule { public virtual string Type => Constants.Unknown; - public virtual string Name { get => Constants.Unknown; set { } } + public virtual string Name + { + get => Constants.Unknown; + set { } + } public virtual string Version => string.Empty; @@ -21,12 +25,14 @@ public class UnknownModule : IModule public IDictionary Env { get; } = ImmutableDictionary.Empty; - public bool Equals(IModule other) => other != null && object.ReferenceEquals(this, other); + public bool Equals(IModule other) => other != null && ReferenceEquals(this, other); } public class UnknownEdgeHubModule : UnknownModule, IEdgeHubModule { - UnknownEdgeHubModule() { } + UnknownEdgeHubModule() + { + } public static UnknownEdgeHubModule Instance { get; } = new UnknownEdgeHubModule(); @@ -38,7 +44,9 @@ public class UnknownEdgeHubModule : UnknownModule, IEdgeHubModule public class UnknownEdgeAgentModule : UnknownModule, IEdgeAgentModule { - UnknownEdgeAgentModule() { } + UnknownEdgeAgentModule() + { + } public static UnknownEdgeAgentModule Instance { get; } = new UnknownEdgeAgentModule(); @@ -52,6 +60,5 @@ public class UnknownEdgeAgentModule : UnknownModule, IEdgeAgentModule public override ModuleStatus DesiredStatus => ModuleStatus.Unknown; public IModule WithRuntimeStatus(ModuleStatus newStatus) => new UnknownEdgeAgentModule(); - } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/AddToStoreCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/AddToStoreCommand.cs index 1267db1a81b..ba04cbe751e 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/AddToStoreCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/AddToStoreCommand.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Commands { using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Newtonsoft.Json; diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/GroupCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/GroupCommand.cs index 6029561008f..65b16687b03 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/GroupCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/GroupCommand.cs @@ -16,7 +16,7 @@ public class GroupCommand : ICommand public GroupCommand(params ICommand[] group) { this.commandGroup = Preconditions.CheckNotNull(group, nameof(group)); - this.id = new Lazy(() => this.commandGroup.Aggregate("", (prev, command) => command.Id + prev)); + this.id = new Lazy(() => this.commandGroup.Aggregate(string.Empty, (prev, command) => command.Id + prev)); } // We use the sum of the IDs of the underlying commands as the id for this group diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommand.cs index ff50cb725f3..7cdba33f24a 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommand.cs @@ -7,12 +7,12 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Commands public class NullCommand : ICommand { - public static NullCommand Instance { get; } = new NullCommand(); - NullCommand() { } + public static NullCommand Instance { get; } = new NullCommand(); + public string Id => string.Empty; public Task ExecuteAsync(CancellationToken token) => TaskEx.Done; diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommandFactory.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommandFactory.cs index 1dfc05f37e0..a0e004560b8 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommandFactory.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/NullCommandFactory.cs @@ -5,12 +5,12 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Commands public class NullCommandFactory : ICommandFactory { - public static NullCommandFactory Instance { get; } = new NullCommandFactory(); - NullCommandFactory() { } + public static NullCommandFactory Instance { get; } = new NullCommandFactory(); + public Task CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => Task.FromResult(NullCommand.Instance); public Task UpdateAsync(IModule current, IModuleWithIdentity next, IRuntimeInfo runtimeInfo) => Task.FromResult(NullCommand.Instance); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/ParallelGroupCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/ParallelGroupCommand.cs index c147292c23c..d4298e59296 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/ParallelGroupCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/ParallelGroupCommand.cs @@ -16,7 +16,7 @@ public class ParallelGroupCommand : ICommand public ParallelGroupCommand(params ICommand[] group) { this.commandGroup = Preconditions.CheckNotNull(group, nameof(group)); - this.id = new Lazy(() => this.commandGroup.Aggregate("", (prev, command) => command.Id + prev)); + this.id = new Lazy(() => this.commandGroup.Aggregate(string.Empty, (prev, command) => command.Id + prev)); } // We use the sum of the IDs of the underlying commands as the id for this group diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/RemoveFromStoreCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/RemoveFromStoreCommand.cs index e7a385e5e9f..d0070b7de22 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/RemoveFromStoreCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/commands/RemoveFromStoreCommand.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Commands { using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; @@ -25,7 +24,7 @@ public RemoveFromStoreCommand(IEntityStore store, string key) public string Show() => $"Saving {this.key} to store"; - // TODO: Consider caching previous value, so that undo can add it back. + // TODO: Consider caching previous value, so that undo can add it back. public Task UndoAsync(CancellationToken token) => Task.CompletedTask; } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigEmptyException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigEmptyException.cs index 599cc840bfb..9c4612ad497 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigEmptyException.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigEmptyException.cs @@ -6,11 +6,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources [Serializable] public class ConfigEmptyException : Exception { - public ConfigEmptyException(string message) : base(message) + public ConfigEmptyException(string message) + : base(message) { } - public ConfigEmptyException(string message, Exception inner) : base(message, inner) + public ConfigEmptyException(string message, Exception inner) + : base(message, inner) { } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigFormatException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigFormatException.cs index e470bac7968..59fd9ef576d 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigFormatException.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/ConfigFormatException.cs @@ -6,11 +6,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources [Serializable] public class ConfigFormatException : Exception { - public ConfigFormatException(string message) : base(message) + public ConfigFormatException(string message) + : base(message) { } - public ConfigFormatException(string message, Exception inner) : base(message, inner) + public ConfigFormatException(string message, Exception inner) + : base(message, inner) { } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupConfigSource.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupConfigSource.cs index f39deea7d0d..f18e5685e67 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupConfigSource.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupConfigSource.cs @@ -31,6 +31,36 @@ public FileBackupConfigSource(string path, IConfigSource underlying, ISerde this.underlying.Configuration; + public async Task GetDeploymentConfigInfoAsync() + { + try + { + DeploymentConfigInfo deploymentConfig = await this.underlying.GetDeploymentConfigInfoAsync(); + if (deploymentConfig == DeploymentConfigInfo.Empty) + { + Events.RestoringFromBackup(deploymentConfig, this.configFilePath); + deploymentConfig = await this.ReadFromBackup(); + } + else if (!deploymentConfig.Exception.HasValue) + { + // TODO - Backing up the config every time for now, probably should optimize this. + await this.BackupDeploymentConfig(deploymentConfig); + } + + return deploymentConfig; + } + catch (Exception ex) + { + Events.RestoringFromBackup(ex, this.configFilePath); + return await this.ReadFromBackup(); + } + } + + public void Dispose() + { + this.underlying?.Dispose(); + } + async Task ReadFromBackup() { DeploymentConfigInfo backedUpDeploymentConfigInfo = DeploymentConfigInfo.Empty; @@ -92,36 +122,6 @@ async Task BackupDeploymentConfig(DeploymentConfigInfo deploymentConfigInfo) } } - public async Task GetDeploymentConfigInfoAsync() - { - try - { - DeploymentConfigInfo deploymentConfig = await this.underlying.GetDeploymentConfigInfoAsync(); - if (deploymentConfig == DeploymentConfigInfo.Empty) - { - Events.RestoringFromBackup(deploymentConfig, this.configFilePath); - deploymentConfig = await this.ReadFromBackup(); - } - else if (!deploymentConfig.Exception.HasValue) - { - // TODO - Backing up the config every time for now, probably should optimize this. - await this.BackupDeploymentConfig(deploymentConfig); - } - - return deploymentConfig; - } - catch (Exception ex) - { - Events.RestoringFromBackup(ex, this.configFilePath); - return await this.ReadFromBackup(); - } - } - - public void Dispose() - { - this.underlying?.Dispose(); - } - static class Events { const int IdStart = AgentEventIds.FileBackupConfigSource; diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupException.cs index b69250bb964..5c0f41f10d1 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupException.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileBackupException.cs @@ -6,11 +6,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources [Serializable] public class FileBackupException : Exception { - public FileBackupException(string message) : base(message) + public FileBackupException(string message) + : base(message) { } - public FileBackupException(string message, Exception inner) : base(message, inner) + public FileBackupException(string message, Exception inner) + : base(message, inner) { } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileConfigSource.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileConfigSource.cs index f728f280887..5a40c14d60c 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileConfigSource.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/FileConfigSource.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Core.Serde; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Concurrency; @@ -41,7 +40,9 @@ public class FileConfigSource : IConfigSource this.watcher.EnableRaisingEvents = true; Events.Created(this.configFilePath); } - + + public IConfiguration Configuration { get; } + public static async Task Create(string configFilePath, IConfiguration configuration, ISerde serde) { Preconditions.CheckNotNull(serde, nameof(serde)); @@ -62,7 +63,13 @@ public static async Task Create(string configFilePath, IConfig return new FileConfigSource(watcher, initial, configuration, serde); } - public IConfiguration Configuration { get; } + public Task GetDeploymentConfigInfoAsync() => Task.FromResult(this.current.Value); + + public void Dispose() + { + this.watcherSubscription.Dispose(); + this.watcher.Dispose(); + } static async Task ReadFromDisk(string path, ISerde serde) { @@ -99,18 +106,10 @@ async void WatcherOnChanged(EventPattern args) } } - public Task GetDeploymentConfigInfoAsync() => Task.FromResult(this.current.Value); - - public void Dispose() - { - this.watcherSubscription.Dispose(); - this.watcher.Dispose(); - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.FileConfigSource; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -127,7 +126,6 @@ public static void NewConfigurationFailed(Exception exception, string filename) { Log.LogError((int)EventIds.NewConfigurationFailed, exception, $"FileConfigSource failed reading new configuration file, {filename}"); } - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/InvalidSchemaVersionException.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/InvalidSchemaVersionException.cs index 3b768e38c6e..44194ad1489 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/InvalidSchemaVersionException.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/configsources/InvalidSchemaVersionException.cs @@ -6,11 +6,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources [Serializable] public class InvalidSchemaVersionException : Exception { - public InvalidSchemaVersionException(string message) : base(message) + public InvalidSchemaVersionException(string message) + : base(message) { } - public InvalidSchemaVersionException(string message, Exception inner) : base(message, inner) + public InvalidSchemaVersionException(string message, Exception inner) + : base(message, inner) { } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/HealthRestartPlanner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/HealthRestartPlanner.cs index ac0190e17fd..e8086fd5cee 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/HealthRestartPlanner.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/HealthRestartPlanner.cs @@ -6,11 +6,11 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Planners using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Core.Commands; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Storage; + using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; + using Newtonsoft.Json; using DiffState = System.ValueTuple< // added modules System.Collections.Generic.IList, @@ -27,7 +27,59 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Planners // modules that are running great System.Collections.Generic.IList >; - using Newtonsoft.Json; + + static class Events + { + const int IdStart = AgentEventIds.HealthRestartPlanner; + static readonly ILogger Log = Logger.Factory.CreateLogger(); + + enum EventIds + { + PlanCreated = IdStart, + ClearRestartStats, + DesiredModules, + CurrentModules, + UnableToUpdateEdgeAgent, + UnableToProcessModule + } + + public static void PlanCreated(IList commands) + { + Log.LogDebug((int)EventIds.PlanCreated, $"HealthRestartPlanner created Plan, with {commands.Count} command(s)."); + } + + public static void ClearingRestartStats(IRuntimeModule module, TimeSpan intensiveCareTime) + { + Log.LogInformation((int)EventIds.ClearRestartStats, $"HealthRestartPlanner is clearing restart stats for module '{module.Name}' as it has been running healthy for {intensiveCareTime}."); + } + + public static void UnableToUpdateEdgeAgent() + { + Log.LogInformation((int)EventIds.UnableToUpdateEdgeAgent, $"Unable to update EdgeAgent module as the EdgeAgent module identity could not be obtained"); + } + + public static void UnableToProcessModule(IModule module) + { + Log.LogInformation((int)EventIds.UnableToProcessModule, $"Unable to process module {module.Name} add or update as the module identity could not be obtained"); + } + + public static void ShutdownPlanCreated(ICommand[] stopCommands) + { + Log.LogDebug((int)EventIds.PlanCreated, $"HealthRestartPlanner created shutdown Plan, with {stopCommands.Length} command(s)."); + } + + internal static void LogDesired(ModuleSet desired) + { + IDictionary modules = desired.Modules.ToImmutableDictionary(); + Log.LogDebug((int)EventIds.DesiredModules, $"List of desired modules is - {JsonConvert.SerializeObject(modules)}"); + } + + internal static void LogCurrent(ModuleSet current) + { + IDictionary modules = current.Modules.ToImmutableDictionary(); + Log.LogDebug((int)EventIds.CurrentModules, $"List of current modules is - {JsonConvert.SerializeObject(modules)}"); + } + } public class HealthRestartPlanner : IPlanner { @@ -40,8 +92,7 @@ public HealthRestartPlanner( ICommandFactory commandFactory, IEntityStore store, TimeSpan intensiveCareTime, - IRestartPolicyManager restartManager - ) + IRestartPolicyManager restartManager) { this.commandFactory = Preconditions.CheckNotNull(commandFactory, nameof(commandFactory)); this.store = Preconditions.CheckNotNull(store, nameof(store)); @@ -49,38 +100,114 @@ IRestartPolicyManager restartManager this.restartManager = Preconditions.CheckNotNull(restartManager, nameof(restartManager)); } + public async Task PlanAsync( + ModuleSet desired, + ModuleSet current, + IRuntimeInfo runtimeInfo, + IImmutableDictionary moduleIdentities) + { + Events.LogDesired(desired); + Events.LogCurrent(current); + // extract list of modules that need attention + (IList added, IList updateDeployed, IList updateStateChanged, IList removed, IList runningGreat) = this.ProcessDiff(desired, current); + + List updateRuntimeCommands = await this.GetUpdateRuntimeCommands(updateDeployed, moduleIdentities, runtimeInfo); + + // create "stop" commands for modules that have been updated/removed + IEnumerable> stopTasks = updateDeployed + .Concat(removed) + .Select(m => this.commandFactory.StopAsync(m)); + IEnumerable stop = await Task.WhenAll(stopTasks); + + // create "remove" commands for modules that are being deleted in this deployment + IEnumerable> removeTasks = removed.Select(m => this.commandFactory.RemoveAsync(m)); + IEnumerable remove = await Task.WhenAll(removeTasks); + + // remove any saved state we might have for modules that are being removed or + // are being updated because of a deployment + IEnumerable> removeStateTasks = removed + .Concat(updateDeployed) + .Select(m => this.commandFactory.WrapAsync(new RemoveFromStoreCommand(this.store, m.Name))); + IEnumerable removeState = await Task.WhenAll(removeStateTasks); + + // create pull, create, update and start commands for added/updated modules + IEnumerable addedCommands = await this.ProcessAddedUpdatedModules( + added, + moduleIdentities, + m => this.commandFactory.CreateAsync(m, runtimeInfo)); + + IEnumerable updatedCommands = await this.ProcessAddedUpdatedModules( + updateDeployed, + moduleIdentities, + m => + { + current.TryGetModule(m.Module.Name, out IModule currentModule); + return this.commandFactory.UpdateAsync( + currentModule, + m, + runtimeInfo); + }); + + // apply restart policy for modules that are not in the deployment list and aren't running + IEnumerable> restartTasks = this.ApplyRestartPolicy(updateStateChanged.Where(m => !m.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase))); + IEnumerable restart = await Task.WhenAll(restartTasks); + + // clear the "restartCount" and "lastRestartTime" values for running modules that have been up + // for more than "IntensiveCareTime" & still have an entry for them in the store + IEnumerable resetHealthStatus = await this.ResetStatsForHealthyModulesAsync(runningGreat); + + IList commands = updateRuntimeCommands + .Concat(stop) + .Concat(remove) + .Concat(removeState) + .Concat(addedCommands) + .Concat(updatedCommands) + .Concat(restart) + .Concat(resetHealthStatus) + .ToList(); + + Events.PlanCreated(commands); + return new Plan(commands); + } + + public async Task CreateShutdownPlanAsync(ModuleSet current) + { + IEnumerable> stopTasks = current.Modules.Values + .Where(c => !c.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)) + .Select(m => this.commandFactory.StopAsync(m)); + ICommand[] stopCommands = await Task.WhenAll(stopTasks); + ICommand parallelCommand = new ParallelGroupCommand(stopCommands); + Events.ShutdownPlanCreated(stopCommands); + return new Plan(new[] { parallelCommand }); + } + IEnumerable> ApplyRestartPolicy(IEnumerable modules) { IEnumerable modulesToBeRestarted = this.restartManager.ApplyRestartPolicy(modules); - IEnumerable> restart = modulesToBeRestarted.Select(async module => - { - ICommand group = new GroupCommand( + IEnumerable> restart = modulesToBeRestarted.Select( + async module => + { // restart the module // await this.commandFactory.RestartAsync(module), - // TODO: Windows native containers have an outstanding bug where "docker restart" // doesn't work. But a "docker stop" followed by a "docker start" will work. Putting // in a temporary workaround to address this. This should be rolled back when the // Windows bug is fixed. - await this.commandFactory.StopAsync(module), - await this.commandFactory.StartAsync(module), - - // Update restart count and last restart time in store - await this.commandFactory.WrapAsync( - new AddToStoreCommand(this.store, module.Name, new ModuleState(module.RestartCount + 1, DateTime.UtcNow)) - ) - ); - - return await this.commandFactory.WrapAsync(group); - }); + ICommand group = new GroupCommand( + await this.commandFactory.StopAsync(module), + await this.commandFactory.StartAsync(module), + // Update restart count and last restart time in store + await this.commandFactory.WrapAsync(new AddToStoreCommand(this.store, module.Name, new ModuleState(module.RestartCount + 1, DateTime.UtcNow)))); + + return await this.commandFactory.WrapAsync(group); + }); return restart; } async Task> ProcessAddedUpdatedModules( IList modules, IImmutableDictionary moduleIdentities, - Func> createUpdateCommandMaker - ) + Func> createUpdateCommandMaker) { // new modules become a command group containing: // create followed by a start command if the desired @@ -182,77 +309,6 @@ DiffState ProcessDiff(ModuleSet desired, ModuleSet current) return (added, updateDeployed, updateStateChanged, removed, runningGreat); } - public async Task PlanAsync(ModuleSet desired, - ModuleSet current, - IRuntimeInfo runtimeInfo, - IImmutableDictionary moduleIdentities) - { - Events.LogDesired(desired); - Events.LogCurrent(current); - // extract list of modules that need attention - (IList added, IList updateDeployed, IList updateStateChanged, IList removed, IList runningGreat) = this.ProcessDiff(desired, current); - - List updateRuntimeCommands = await this.GetUpdateRuntimeCommands(updateDeployed, moduleIdentities, runtimeInfo); - - // create "stop" commands for modules that have been updated/removed - IEnumerable> stopTasks = updateDeployed - .Concat(removed) - .Select(m => this.commandFactory.StopAsync(m)); - IEnumerable stop = await Task.WhenAll(stopTasks); - - // create "remove" commands for modules that are being deleted in this deployment - IEnumerable> removeTasks = removed.Select(m => this.commandFactory.RemoveAsync(m)); - IEnumerable remove = await Task.WhenAll(removeTasks); - - // remove any saved state we might have for modules that are being removed or - // are being updated because of a deployment - IEnumerable> removeStateTasks = removed - .Concat(updateDeployed) - .Select(m => this.commandFactory.WrapAsync(new RemoveFromStoreCommand(this.store, m.Name))); - IEnumerable removeState = await Task.WhenAll(removeStateTasks); - - // create pull, create, update and start commands for added/updated modules - IEnumerable addedCommands = await this.ProcessAddedUpdatedModules( - added, - moduleIdentities, - m => this.commandFactory.CreateAsync(m, runtimeInfo) - ); - - IEnumerable updatedCommands = await this.ProcessAddedUpdatedModules( - updateDeployed, - moduleIdentities, - m => - { - current.TryGetModule(m.Module.Name, out IModule currentModule); - return this.commandFactory.UpdateAsync( - currentModule, - m, - runtimeInfo); - } - ); - - // apply restart policy for modules that are not in the deployment list and aren't running - IEnumerable> restartTasks = this.ApplyRestartPolicy(updateStateChanged.Where(m => !m.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase))); - IEnumerable restart = await Task.WhenAll(restartTasks); - - // clear the "restartCount" and "lastRestartTime" values for running modules that have been up - // for more than "IntensiveCareTime" & still have an entry for them in the store - IEnumerable resetHealthStatus = await this.ResetStatsForHealthyModulesAsync(runningGreat); - - IList commands = updateRuntimeCommands - .Concat(stop) - .Concat(remove) - .Concat(removeState) - .Concat(addedCommands) - .Concat(updatedCommands) - .Concat(restart) - .Concat(resetHealthStatus) - .ToList(); - - Events.PlanCreated(commands); - return new Plan(commands); - } - async Task> GetUpdateRuntimeCommands(IList updateDeployed, IImmutableDictionary moduleIdentities, IRuntimeInfo runtimeInfo) { var updateRuntimeCommands = new List(); @@ -273,69 +329,5 @@ async Task> GetUpdateRuntimeCommands(IList updateDeploye return updateRuntimeCommands; } - - public async Task CreateShutdownPlanAsync(ModuleSet current) - { - IEnumerable> stopTasks = current.Modules.Values - .Where(c => !c.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)) - .Select(m => this.commandFactory.StopAsync(m)); - ICommand[] stopCommands = await Task.WhenAll(stopTasks); - ICommand parallelCommand = new ParallelGroupCommand(stopCommands); - Events.ShutdownPlanCreated(stopCommands); - return new Plan(new[] { parallelCommand }); - } - } - - static class Events - { - static readonly ILogger Log = Logger.Factory.CreateLogger(); - const int IdStart = AgentEventIds.HealthRestartPlanner; - - enum EventIds - { - PlanCreated = IdStart, - ClearRestartStats, - DesiredModules, - CurrentModules, - UnableToUpdateEdgeAgent, - UnableToProcessModule - } - - public static void PlanCreated(IList commands) - { - Log.LogDebug((int)EventIds.PlanCreated, $"HealthRestartPlanner created Plan, with {commands.Count} command(s)."); - } - - public static void ClearingRestartStats(IRuntimeModule module, TimeSpan intensiveCareTime) - { - Log.LogInformation((int)EventIds.ClearRestartStats, $"HealthRestartPlanner is clearing restart stats for module '{module.Name}' as it has been running healthy for {intensiveCareTime}."); - } - - internal static void LogDesired(ModuleSet desired) - { - IDictionary modules = desired.Modules.ToImmutableDictionary(); - Log.LogDebug((int)EventIds.DesiredModules, $"List of desired modules is - {JsonConvert.SerializeObject(modules)}"); - } - - internal static void LogCurrent(ModuleSet current) - { - IDictionary modules = current.Modules.ToImmutableDictionary(); - Log.LogDebug((int)EventIds.CurrentModules, $"List of current modules is - {JsonConvert.SerializeObject(modules)}"); - } - - public static void UnableToUpdateEdgeAgent() - { - Log.LogInformation((int)EventIds.UnableToUpdateEdgeAgent, $"Unable to update EdgeAgent module as the EdgeAgent module identity could not be obtained"); - } - - public static void UnableToProcessModule(IModule module) - { - Log.LogInformation((int)EventIds.UnableToProcessModule, $"Unable to process module {module.Name} add or update as the module identity could not be obtained"); - } - - public static void ShutdownPlanCreated(ICommand[] stopCommands) - { - Log.LogDebug((int)EventIds.PlanCreated, $"HealthRestartPlanner created shutdown Plan, with {stopCommands.Length} command(s)."); - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/RestartPlanner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/RestartPlanner.cs index cdd4ee2d60a..65ad18077b4 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/RestartPlanner.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planners/RestartPlanner.cs @@ -25,7 +25,7 @@ public RestartPlanner(ICommandFactory commandFactory) this.commandFactory = Preconditions.CheckNotNull(commandFactory, nameof(commandFactory)); } - async public Task PlanAsync(ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary moduleIdentities) + public async Task PlanAsync(ModuleSet desired, ModuleSet current, IRuntimeInfo runtimeInfo, IImmutableDictionary moduleIdentities) { Diff diff = desired.Diff(current); Plan plan = diff.IsEmpty @@ -35,6 +35,17 @@ async public Task PlanAsync(ModuleSet desired, ModuleSet current, IRuntime return plan; } + public async Task CreateShutdownPlanAsync(ModuleSet current) + { + IEnumerable> stopTasks = current.Modules.Values + .Where(c => !c.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)) + .Select(m => this.commandFactory.StopAsync(m)); + ICommand[] stopCommands = await Task.WhenAll(stopTasks); + ICommand parallelCommand = new ParallelGroupCommand(stopCommands); + Events.ShutdownPlanCreated(stopCommands); + return new Plan(new[] { parallelCommand }); + } + async Task CreatePlan(ModuleSet desired, ModuleSet current, Diff diff, IRuntimeInfo runtimeInfo, IImmutableDictionary moduleIdentities) { IEnumerable> stopTasks = current.Modules.Select(m => this.commandFactory.StopAsync(m.Value)); @@ -61,17 +72,6 @@ async Task CreatePlan(ModuleSet desired, ModuleSet current, Diff diff, IRu return new Plan(commands); } - public async Task CreateShutdownPlanAsync(ModuleSet current) - { - IEnumerable> stopTasks = current.Modules.Values - .Where(c => !c.Name.Equals(Constants.EdgeAgentModuleName, StringComparison.OrdinalIgnoreCase)) - .Select(m => this.commandFactory.StopAsync(m)); - ICommand[] stopCommands = await Task.WhenAll(stopTasks); - ICommand parallelCommand = new ParallelGroupCommand(stopCommands); - Events.ShutdownPlanCreated(stopCommands); - return new Plan(new[] { parallelCommand }); - } - async Task CreateOrUpdate(ModuleSet current, IModule desiredMod, IRuntimeInfo runtimeInfo, IImmutableDictionary moduleIdentities) => current.TryGetModule(desiredMod.Name, out IModule currentMod) ? await this.commandFactory.UpdateAsync(currentMod, new ModuleWithIdentity(desiredMod, moduleIdentities.GetValueOrDefault(desiredMod.Name)), runtimeInfo) @@ -79,8 +79,8 @@ async Task CreateOrUpdate(ModuleSet current, IModule desiredMod, IRunt static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.RestartPlanner; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrderedPlanRunner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrderedPlanRunner.cs index c007726ce8a..0b08aa79d44 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrderedPlanRunner.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrderedPlanRunner.cs @@ -33,6 +33,7 @@ public async Task ExecuteAsync(long deploymentId, Plan plan, CancellationT { failures = Option.Some(new List()); } + failures.ForEach(f => f.Add(ex)); } } @@ -44,8 +45,8 @@ public async Task ExecuteAsync(long deploymentId, Plan plan, CancellationT static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.OrderedPlanRunner; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrdererdRetryPlanRunner.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrdererdRetryPlanRunner.cs index 9d7d7cd962e..070d7d6f9e7 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrdererdRetryPlanRunner.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/planrunners/OrdererdRetryPlanRunner.cs @@ -12,20 +12,22 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.PlanRunners public class OrderedRetryPlanRunner : IPlanRunner { readonly AsyncLock sync; - long lastDeploymentId; - Dictionary commandRunStatus; + readonly Dictionary commandRunStatus; readonly int maxRunCount; readonly int coolOffTimeUnitInSeconds; readonly ISystemTime systemTime; + long lastDeploymentId; public OrderedRetryPlanRunner(int maxRunCount, int coolOffTimeUnitInSeconds, ISystemTime systemTime) { this.maxRunCount = Preconditions.CheckRange( - maxRunCount, 1, nameof(maxRunCount) - ); + maxRunCount, + 1, + nameof(maxRunCount)); this.coolOffTimeUnitInSeconds = Preconditions.CheckRange( - coolOffTimeUnitInSeconds, 0, nameof(coolOffTimeUnitInSeconds) - ); + coolOffTimeUnitInSeconds, + 0, + nameof(coolOffTimeUnitInSeconds)); this.systemTime = Preconditions.CheckNotNull(systemTime, nameof(systemTime)); this.sync = new AsyncLock(); this.lastDeploymentId = -1; @@ -100,11 +102,11 @@ public async Task ExecuteAsync(long deploymentId, Plan plan, CancellationT { failures = Option.Some(new List()); } + failures.ForEach(f => f.Add(ex)); // since this command failed, record its status - int newRunCount = this.commandRunStatus.ContainsKey(command.Id) ? - this.commandRunStatus[command.Id].RunCount : 0; + int newRunCount = this.commandRunStatus.ContainsKey(command.Id) ? this.commandRunStatus[command.Id].RunCount : 0; this.commandRunStatus[command.Id] = new CommandRunStats(newRunCount + 1, this.systemTime.UtcNow, ex); } } @@ -115,13 +117,7 @@ public async Task ExecuteAsync(long deploymentId, Plan plan, CancellationT } } - private - ( - bool shouldRun, - int runCount, - TimeSpan coolOffPeriod, - TimeSpan elapsedTime - ) ShouldRunCommand(ICommand command) + (bool shouldRun, int runCount, TimeSpan coolOffPeriod, TimeSpan elapsedTime) ShouldRunCommand(ICommand command) { // the command should be run if there's no entry for it in our status dictionary if (this.commandRunStatus.ContainsKey(command.Id) == false) @@ -139,8 +135,7 @@ TimeSpan elapsedTime } TimeSpan coolOffPeriod = TimeSpan.FromSeconds( - this.coolOffTimeUnitInSeconds * Math.Pow(2, commandRunStatus.RunCount) - ); + this.coolOffTimeUnitInSeconds * Math.Pow(2, commandRunStatus.RunCount)); TimeSpan elapsedTime = this.systemTime.UtcNow - commandRunStatus.LastRunTimeUtc; return (elapsedTime > coolOffPeriod, commandRunStatus.RunCount, coolOffPeriod, elapsedTime); @@ -148,11 +143,6 @@ TimeSpan elapsedTime class CommandRunStats { - public int RunCount { get; } - public DateTime LastRunTimeUtc { get; } - public Option Exception { get; } - public bool LoggedWarning { get; set; } - public static readonly CommandRunStats Default = new CommandRunStats(0, DateTime.MinValue); public CommandRunStats(int runCount, DateTime lastRunTimeUtc, Exception exception = null) @@ -162,12 +152,20 @@ public CommandRunStats(int runCount, DateTime lastRunTimeUtc, Exception exceptio this.Exception = Option.Maybe(exception); this.LoggedWarning = false; } + + public int RunCount { get; } + + public DateTime LastRunTimeUtc { get; } + + public Option Exception { get; } + + public bool LoggedWarning { get; set; } } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.OrderedRetryPlanRunner; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/reporters/NullReporter.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/reporters/NullReporter.cs index 10fb505bf34..9e62102bf83 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/reporters/NullReporter.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/reporters/NullReporter.cs @@ -7,13 +7,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Reporters public class NullReporter : IReporter { - private NullReporter() { } + NullReporter() + { + } public static NullReporter Instance { get; } = new NullReporter(); public Task ReportAsync(CancellationToken token, ModuleSet moduleSet, IRuntimeInfo runtimeInfo, long version, Option status) => Task.CompletedTask; public Task ReportShutdown(DeploymentStatus status, CancellationToken token) => Task.CompletedTask; - } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/DiffSerde.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/DiffSerde.cs index 82f04e94981..7e3a69736be 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/DiffSerde.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/DiffSerde.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Serde public class DiffSerde : ISerde { readonly IDictionary converters; - + public DiffSerde(IDictionary deserializerTypes) { this.converters = new Dictionary(Preconditions.CheckNotNull(deserializerTypes, nameof(deserializerTypes)), StringComparer.OrdinalIgnoreCase); @@ -18,7 +18,8 @@ public DiffSerde(IDictionary deserializerTypes) public string Serialize(Diff diff) => throw new NotSupportedException(); - public T Deserialize(string json) where T : Diff => throw new NotSupportedException(); + public T Deserialize(string json) + where T : Diff => throw new NotSupportedException(); public Diff Deserialize(string json) { @@ -28,12 +29,11 @@ public Diff Deserialize(string json) } } - class DiffJsonConverter : JsonConverter { readonly IDictionary converters; - public DiffJsonConverter(IDictionary deserializerTypes) + public DiffJsonConverter(IDictionary deserializerTypes) { this.converters = new Dictionary(deserializerTypes, StringComparer.OrdinalIgnoreCase); } @@ -67,6 +67,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { throw new JsonSerializationException($"Could not find right converter given type {converterType.Value()}"); } + IModule module = ModuleSerde.Instance.Deserialize(xtokenFirst.ToString(), serializeType); module.Name = name; updateList.Add(module); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ISerde.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ISerde.cs index 7fddcb67247..f4202144459 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ISerde.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ISerde.cs @@ -7,6 +7,7 @@ public interface ISerde T Deserialize(string json); - T1 Deserialize(string json) where T1 : T; + T1 Deserialize(string json) + where T1 : T; } -} \ No newline at end of file +} diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSerde.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSerde.cs index 3cd66775c58..4b1959c52ed 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSerde.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSerde.cs @@ -6,20 +6,21 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Serde public class ModuleSerde : ISerde { - public static ModuleSerde Instance { get; } = new ModuleSerde(); - ModuleSerde() { } + public static ModuleSerde Instance { get; } = new ModuleSerde(); + public string Serialize(IModule module) => JsonConvert.SerializeObject(module); public IModule Deserialize(string json) => this.Deserialize(json); - public T Deserialize(string json) where T : IModule + public T Deserialize(string json) + where T : IModule { - //This try/catch is needed because NewtonSoft Deserialize is calling the constructor even - //if the Name parameter is not present on the JSON. + // This try/catch is needed because NewtonSoft Deserialize is calling the constructor even + // if the Name parameter is not present on the JSON. try { return JsonConvert.DeserializeObject(json); @@ -34,10 +35,10 @@ public T Deserialize(string json) where T : IModule } } - public IModule Deserialize(string json, System.Type serializerType) + public IModule Deserialize(string json, Type serializerType) { - //This try/catch is needed because NewtonSoft Deserialize is calling the constructor even - //if the Name parameter is not present on the JSON. + // This try/catch is needed because NewtonSoft Deserialize is calling the constructor even + // if the Name parameter is not present on the JSON. try { return (IModule)JsonConvert.DeserializeObject(json, serializerType); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSetSerde.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSetSerde.cs index 50df525048e..dc60e180686 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSetSerde.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/ModuleSetSerde.cs @@ -17,8 +17,7 @@ public ModuleSetSerde(IDictionary deserializerTypes) { IDictionary converters = new Dictionary( Preconditions.CheckNotNull(deserializerTypes, nameof(deserializerTypes)), - StringComparer.OrdinalIgnoreCase - ); + StringComparer.OrdinalIgnoreCase); this.jsonSerializerSettings = new JsonSerializerSettings { @@ -35,7 +34,8 @@ public ModuleSetSerde(IDictionary deserializerTypes) public ModuleSet Deserialize(string json) => this.Deserialize(json); - public T Deserialize(string json) where T : ModuleSet + public T Deserialize(string json) + where T : ModuleSet { try { @@ -47,24 +47,6 @@ public T Deserialize(string json) where T : ModuleSet } } - class ModuleSetJsonConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException(); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - Dictionary modules = new Dictionary>(serializer.Deserialize>>(reader), StringComparer.OrdinalIgnoreCase) - .GetOrElse("modules", new Dictionary()) - .ToDictionary(pair => pair.Key, pair => { pair.Value.Name = pair.Key; return pair.Value; }); - - return new ModuleSet(modules); - } - - public override bool CanWrite => false; - - public override bool CanConvert(Type objectType) => objectType == typeof(ModuleSet); - } - class ModuleJsonConverter : JsonConverter { readonly IDictionary converters; @@ -99,5 +81,29 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist public override bool CanConvert(Type objectType) => objectType == typeof(IModule); } + + class ModuleSetJsonConverter : JsonConverter + { + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException(); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Dictionary modules = new Dictionary>(serializer.Deserialize>>(reader), StringComparer.OrdinalIgnoreCase) + .GetOrElse("modules", new Dictionary()) + .ToDictionary( + pair => pair.Key, + pair => + { + pair.Value.Name = pair.Key; + return pair.Value; + }); + + return new ModuleSet(modules); + } + + public override bool CanConvert(Type objectType) => objectType == typeof(ModuleSet); + } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/TypeSpecificSerDe.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/TypeSpecificSerDe.cs index 719c334f46f..3d413e378df 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/TypeSpecificSerDe.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Core/serde/TypeSpecificSerDe.cs @@ -8,8 +8,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Serde using Newtonsoft.Json.Linq; /// - /// SerDe for objects with types that depend on the "type" property - /// (for example Docker specific types, etc.) + /// SerDe for objects with types that depend on the "type" property + /// (for example Docker specific types, etc.) /// public class TypeSpecificSerDe : ISerde { @@ -22,7 +22,7 @@ public class TypeSpecificSerDe : ISerde /// IRuntimeInfo -> /// "docker" -> DockerRuntimeInfo /// This enables supporting multiple interfaces in an object, for different "types" like Docker. - /// + /// public TypeSpecificSerDe(IDictionary> deserializerTypesMap) { Preconditions.CheckNotNull(deserializerTypesMap, nameof(deserializerTypesMap)); @@ -43,7 +43,8 @@ public string Serialize(T value) public T Deserialize(string json) => this.Deserialize(json); - public TU Deserialize(string json) where TU : T + public TU Deserialize(string json) + where TU : T { try { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/CombinedDockerConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/CombinedDockerConfig.cs index b84f39381ee..f2b25476b4a 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/CombinedDockerConfig.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/CombinedDockerConfig.cs @@ -8,6 +8,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker public class CombinedDockerConfig { + public CombinedDockerConfig(string image, CreateContainerParameters createOptions, Option authConfig) + { + this.Image = Preconditions.CheckNonWhiteSpace(image, nameof(image)).Trim(); + this.CreateOptions = Preconditions.CheckNotNull(createOptions, nameof(createOptions)); + this.AuthConfig = authConfig; + } + [JsonProperty(Required = Required.Always, PropertyName = "image")] public string Image { get; } @@ -17,12 +24,5 @@ public class CombinedDockerConfig [JsonProperty(Required = Required.AllowNull, PropertyName = "auth")] [JsonConverter(typeof(OptionConverter))] public Option AuthConfig { get; } - - public CombinedDockerConfig(string image, CreateContainerParameters createOptions, Option authConfig) - { - this.Image = Preconditions.CheckNonWhiteSpace(image, nameof(image)).Trim(); - this.CreateOptions = Preconditions.CheckNotNull(createOptions, nameof(createOptions)); - this.AuthConfig = authConfig; - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Constants.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Constants.cs index fd47b156f44..b12ccf2edf9 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Constants.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Constants.cs @@ -9,6 +9,6 @@ static class Constants public const int TwinValueMaxSize = 512; - public const int TwinValueMaxChunks = 100; // The chunks sequence number is two bytes, which allows 100 chunks [0, 100) + public const int TwinValueMaxChunks = 100; // The chunks sequence number is two bytes, which allows 100 chunks [0, 100) } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerCommandFactory.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerCommandFactory.cs index d2efd69e9d0..20978883dce 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerCommandFactory.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerCommandFactory.cs @@ -34,6 +34,7 @@ public async Task CreateAsync(IModuleWithIdentity module, IRuntimeInfo new PullCommand(this.client, combinedDockerConfig), await CreateCommand.BuildAsync(this.client, dockerModule, module.ModuleIdentity, this.dockerLoggerConfig, this.configSource, module.Module is EdgeHubDockerModule)); } + return NullCommand.Instance; } @@ -45,28 +46,33 @@ public async Task UpdateAsync(IModule current, IModuleWithIdentity nex new RemoveCommand(this.client, currentDockerModule), await this.CreateAsync(next, runtimeInfo)); } + return NullCommand.Instance; } public Task RemoveAsync(IModule module) => - Task.FromResult(module is DockerModule - ? new RemoveCommand(this.client, (DockerModule)module) - : (ICommand)NullCommand.Instance); + Task.FromResult( + module is DockerModule + ? new RemoveCommand(this.client, (DockerModule)module) + : (ICommand)NullCommand.Instance); public Task StartAsync(IModule module) => - Task.FromResult(module is DockerModule - ? new StartCommand(this.client, (DockerModule)module) - : (ICommand)NullCommand.Instance); + Task.FromResult( + module is DockerModule + ? new StartCommand(this.client, (DockerModule)module) + : (ICommand)NullCommand.Instance); public Task StopAsync(IModule module) => - Task.FromResult(module is DockerModule - ? new StopCommand(this.client, (DockerModule)module) - : (ICommand)NullCommand.Instance); + Task.FromResult( + module is DockerModule + ? new StopCommand(this.client, (DockerModule)module) + : (ICommand)NullCommand.Instance); public Task RestartAsync(IModule module) => - Task.FromResult(module is DockerRuntimeModule - ? new RestartCommand(this.client, (DockerRuntimeModule)module) - : (ICommand)NullCommand.Instance); + Task.FromResult( + module is DockerRuntimeModule + ? new RestartCommand(this.client, (DockerRuntimeModule)module) + : (ICommand)NullCommand.Instance); public Task WrapAsync(ICommand command) => Task.FromResult(command); } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerConfig.cs index fa1044e9f64..a37f01ea1d3 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerConfig.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerConfig.cs @@ -13,12 +13,6 @@ public class DockerConfig : IEquatable { readonly CreateContainerParameters createOptions; - public string Image { get; } - - // Do a serialization roundtrip to clone the createOptions - // https://docs.docker.com/engine/api/v1.25/#operation/ContainerCreate - public CreateContainerParameters CreateOptions => JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this.createOptions)); - public DockerConfig(string image) : this(image, string.Empty) { @@ -38,13 +32,19 @@ public DockerConfig(string image, CreateContainerParameters createOptions) this.createOptions = Preconditions.CheckNotNull(createOptions, nameof(createOptions)); } + public string Image { get; } + + // Do a serialization roundtrip to clone the createOptions + // https://docs.docker.com/engine/api/v1.25/#operation/ContainerCreate + public CreateContainerParameters CreateOptions => JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this.createOptions)); + public override bool Equals(object obj) => this.Equals(obj as DockerConfig); public override int GetHashCode() { unchecked { - int hashCode = (this.Image != null ? this.Image.GetHashCode() : 0); + int hashCode = this.Image != null ? this.Image.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (this.createOptions?.GetHashCode() ?? 0); return hashCode; } @@ -56,13 +56,55 @@ public bool Equals(DockerConfig other) { return false; } + if (ReferenceEquals(this, other)) { return true; } return string.Equals(this.Image, other.Image) && - CompareCreateOptions(this.CreateOptions, other.CreateOptions); + CompareCreateOptions(this.CreateOptions, other.CreateOptions); + } + + static bool CompareCreateOptions(CreateContainerParameters a, CreateContainerParameters b) + { + bool result; + + if ((a != null) && (b != null)) + { + string aValue = null; + string bValue = null; + + // Remove the `normalizedCreateOptions` labels from comparison consideration + if (a.Labels?.TryGetValue("normalizedCreateOptions", out aValue) ?? false) + { + a.Labels?.Remove("normalizedCreateOptions"); + } + + if (b.Labels?.TryGetValue("normalizedCreateOptions", out bValue) ?? false) + { + b.Labels?.Remove("normalizedCreateOptions"); + } + + result = JsonConvert.SerializeObject(a).Equals(JsonConvert.SerializeObject(b)); + + // Restore `normalizedCreateOptions` labels + if (aValue != null) + { + a.Labels.Add("normalizedCreateOptions", aValue); + } + + if (bValue != null) + { + b.Labels.Add("normalizedCreateOptions", bValue); + } + } + else + { + result = a == b; + } + + return result; } class DockerConfigJsonConverter : JsonConverter @@ -109,44 +151,5 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist public override bool CanConvert(Type objectType) => objectType == typeof(DockerConfig); } - - static bool CompareCreateOptions(CreateContainerParameters a, CreateContainerParameters b) - { - bool result; - - if ((a != null) && (b != null)) - { - string aValue = null; - string bValue = null; - - // Remove the `normalizedCreateOptions` labels from comparison consideration - if (a.Labels?.TryGetValue("normalizedCreateOptions", out aValue) ?? false) - { - a.Labels?.Remove("normalizedCreateOptions"); - } - if (b.Labels?.TryGetValue("normalizedCreateOptions", out bValue) ?? false) - { - b.Labels?.Remove("normalizedCreateOptions"); - } - - result = JsonConvert.SerializeObject(a).Equals(JsonConvert.SerializeObject(b)); - - // Restore `normalizedCreateOptions` labels - if (aValue != null) - { - a.Labels.Add("normalizedCreateOptions", aValue); - } - if (bValue != null) - { - b.Labels.Add("normalizedCreateOptions", bValue); - } - } - else - { - result = (a == b); - } - - return result; - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerDesiredModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerDesiredModule.cs index 77ef51dd220..d552861f35e 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerDesiredModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerDesiredModule.cs @@ -9,25 +9,30 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker public class DockerDesiredModule : DockerModule { + [JsonConstructor] + DockerDesiredModule( + string version, + ModuleStatus desiredStatus, + RestartPolicy restartPolicy, + string type, + DockerConfig settings, + ConfigurationInfo configuration, + IDictionary env) + : base(string.Empty, version, desiredStatus, restartPolicy, settings, configuration, env) + { + Preconditions.CheckArgument(type?.Equals("docker") ?? false); + this.DesiredStatus = Preconditions.CheckIsDefined(desiredStatus); + this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); + } + [JsonProperty(Required = Required.Always, PropertyName = "status")] public override ModuleStatus DesiredStatus { get; } [JsonProperty( PropertyName = "restartPolicy", Required = Required.DisallowNull, - DefaultValueHandling = DefaultValueHandling.Populate - )] + DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue(Core.Constants.DefaultRestartPolicy)] public override RestartPolicy RestartPolicy { get; } - - [JsonConstructor] - DockerDesiredModule(string version, ModuleStatus desiredStatus, RestartPolicy restartPolicy, string type, - DockerConfig settings, ConfigurationInfo configuration, IDictionary env) - : base(string.Empty, version, desiredStatus, restartPolicy, settings, configuration, env) - { - Preconditions.CheckArgument(type?.Equals("docker") ?? false); - this.DesiredStatus = Preconditions.CheckIsDefined(desiredStatus); - this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironment.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironment.cs index 24f5ecbd8a9..484f66aea71 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironment.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironment.cs @@ -26,7 +26,8 @@ public class DockerEnvironment : IEnvironment readonly DeploymentConfig deploymentConfig; readonly IRestartPolicyManager restartManager; - public DockerEnvironment(IRuntimeInfoProvider moduleStatusProvider, + public DockerEnvironment( + IRuntimeInfoProvider moduleStatusProvider, DeploymentConfig deploymentConfig, IEntityStore moduleStateStore, IRestartPolicyManager restartManager, @@ -74,29 +75,54 @@ public async Task GetModulesAsync(CancellationToken token) { case Core.Constants.EdgeHubModuleName: module = new EdgeHubDockerRuntimeModule( - dockerModule.DesiredStatus, dockerModule.RestartPolicy, dockerReportedConfig, - (int)dockerRuntimeInfo.ExitCode, dockerRuntimeInfo.Description, dockerRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), - lastExitTime, moduleState.RestartCount, moduleState.LastRestartTimeUtc, - moduleRuntimeStatus, dockerModule.ConfigurationInfo, dockerModule.Env); + dockerModule.DesiredStatus, + dockerModule.RestartPolicy, + dockerReportedConfig, + (int)dockerRuntimeInfo.ExitCode, + dockerRuntimeInfo.Description, + dockerRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), + lastExitTime, + moduleState.RestartCount, + moduleState.LastRestartTimeUtc, + moduleRuntimeStatus, + dockerModule.ConfigurationInfo, + dockerModule.Env); break; case Core.Constants.EdgeAgentModuleName: - module = new EdgeAgentDockerRuntimeModule(dockerReportedConfig, moduleRuntimeStatus, - (int)dockerRuntimeInfo.ExitCode, dockerRuntimeInfo.Description, dockerRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), - lastExitTime, dockerModule.ConfigurationInfo, dockerModule.Env); + module = new EdgeAgentDockerRuntimeModule( + dockerReportedConfig, + moduleRuntimeStatus, + (int)dockerRuntimeInfo.ExitCode, + dockerRuntimeInfo.Description, + dockerRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), + lastExitTime, + dockerModule.ConfigurationInfo, + dockerModule.Env); break; default: module = new DockerRuntimeModule( - moduleRuntimeInfo.Name, dockerModule.Version, dockerModule.DesiredStatus, dockerModule.RestartPolicy, dockerReportedConfig, - (int)moduleRuntimeInfo.ExitCode, moduleRuntimeInfo.Description, moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), lastExitTime, - moduleState.RestartCount, moduleState.LastRestartTimeUtc, - moduleRuntimeStatus, dockerModule.ConfigurationInfo, dockerModule.Env); + moduleRuntimeInfo.Name, + dockerModule.Version, + dockerModule.DesiredStatus, + dockerModule.RestartPolicy, + dockerReportedConfig, + (int)moduleRuntimeInfo.ExitCode, + moduleRuntimeInfo.Description, + moduleRuntimeInfo.StartTime.GetOrElse(DateTime.MinValue), + lastExitTime, + moduleState.RestartCount, + moduleState.LastRestartTimeUtc, + moduleRuntimeStatus, + dockerModule.ConfigurationInfo, + dockerModule.Env); break; } modules.Add(module); } + return new ModuleSet(modules.ToDictionary(m => m.Name, m => m)); } @@ -120,8 +146,8 @@ public Task GetRuntimeInfoAsync() static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.DockerEnvironment; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironmentProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironmentProvider.cs index 31ace522e58..cfc8d14e93d 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironmentProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerEnvironmentProvider.cs @@ -31,20 +31,29 @@ public class DockerEnvironmentProvider : IEnvironmentProvider this.restartPolicyManager = Preconditions.CheckNotNull(restartPolicyManager, nameof(restartPolicyManager)); } - public static async Task CreateAsync(IRuntimeInfoProvider runtimeInfoProvider, IEntityStore store, + public static async Task CreateAsync( + IRuntimeInfoProvider runtimeInfoProvider, + IEntityStore store, IRestartPolicyManager restartPolicyManager) { SystemInfo systemInfo = await Preconditions.CheckNotNull(runtimeInfoProvider, nameof(runtimeInfoProvider)).GetSystemInfo(); return new DockerEnvironmentProvider( - runtimeInfoProvider, store, restartPolicyManager, - systemInfo.OperatingSystemType, systemInfo.Architecture, + runtimeInfoProvider, + store, + restartPolicyManager, + systemInfo.OperatingSystemType, + systemInfo.Architecture, systemInfo.Version); } public IEnvironment Create(DeploymentConfig deploymentConfig) => new DockerEnvironment( - this.moduleStatusProvider, deploymentConfig, this.store, - this.restartPolicyManager, this.operatingSystemType, - this.architecture, this.version); + this.moduleStatusProvider, + deploymentConfig, + this.store, + this.restartPolicyManager, + this.operatingSystemType, + this.architecture, + this.version); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerLoggingConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerLoggingConfig.cs index 45f66880443..11d5cec0637 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerLoggingConfig.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerLoggingConfig.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Agent.Docker { using System.Collections.Generic; @@ -9,16 +9,9 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker public class DockerLoggingConfig { - [JsonProperty(PropertyName = "type")] - public string Type { get; } - - [JsonProperty(Required = Required.AllowNull, PropertyName = "config")] - public IDictionary Config { get; } - - public DockerLoggingConfig(string type) : - this(type, ImmutableDictionary.Empty) + public DockerLoggingConfig(string type) + : this(type, ImmutableDictionary.Empty) { - } [JsonConstructor] @@ -28,19 +21,28 @@ public DockerLoggingConfig(string type, IDictionary config) this.Config = Preconditions.CheckNotNull(config, nameof(config)); } - public override bool Equals(object obj) => this.Equals(obj as DockerLoggingConfig); + [JsonProperty(PropertyName = "type")] + public string Type { get; } - bool ConfigEquals(IDictionary config1, IDictionary config2) => - config1.Count == config2.Count && !config1.Except(config2).Any(); + [JsonProperty(Required = Required.AllowNull, PropertyName = "config")] + public IDictionary Config { get; } + + public override bool Equals(object obj) => this.Equals(obj as DockerLoggingConfig); public bool Equals(DockerLoggingConfig other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return this.ConfigEquals(this.Config, other.Config) && - string.Equals(this.Type, other.Type); + string.Equals(this.Type, other.Type); } public override int GetHashCode() @@ -53,5 +55,7 @@ public override int GetHashCode() } } + bool ConfigEquals(IDictionary config1, IDictionary config2) => + config1.Count == config2.Count && !config1.Except(config2).Any(); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerModule.cs index e1c26d94fe2..7a5785580f6 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerModule.cs @@ -11,6 +11,24 @@ public class DockerModule : IModule { static readonly DictionaryComparer EnvDictionaryComparer = new DictionaryComparer(); + public DockerModule( + string name, + string version, + ModuleStatus desiredStatus, + RestartPolicy restartPolicy, + DockerConfig config, + ConfigurationInfo configurationInfo, + IDictionary env) + { + this.Name = name; + this.Version = version ?? string.Empty; + this.DesiredStatus = Preconditions.CheckIsDefined(desiredStatus); + this.Config = Preconditions.CheckNotNull(config, nameof(config)); + this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); + this.ConfigurationInfo = configurationInfo ?? new ConfigurationInfo(string.Empty); + this.Env = env?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; + } + [JsonIgnore] public string Name { get; set; } @@ -35,18 +53,6 @@ public class DockerModule : IModule [JsonProperty(PropertyName = "env")] public IDictionary Env { get; } - public DockerModule(string name, string version, ModuleStatus desiredStatus, RestartPolicy restartPolicy, - DockerConfig config, ConfigurationInfo configurationInfo, IDictionary env) - { - this.Name = name; - this.Version = version ?? string.Empty; - this.DesiredStatus = Preconditions.CheckIsDefined(desiredStatus); - this.Config = Preconditions.CheckNotNull(config, nameof(config)); - this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); - this.ConfigurationInfo = configurationInfo ?? new ConfigurationInfo(string.Empty); - this.Env = env?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; - } - public override bool Equals(object obj) => this.Equals(obj as DockerModule); public virtual bool Equals(IModule other) => this.Equals(other as DockerModule); @@ -54,26 +60,32 @@ public DockerModule(string name, string version, ModuleStatus desiredStatus, Res public virtual bool Equals(IModule other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return string.Equals(this.Name, other.Name) && - string.Equals(this.Version, other.Version) && - string.Equals(this.Type, other.Type) && - this.DesiredStatus == other.DesiredStatus && - this.Config.Equals(other.Config) && - this.RestartPolicy == other.RestartPolicy && - EnvDictionaryComparer.Equals(this.Env, other.Env); + string.Equals(this.Version, other.Version) && + string.Equals(this.Type, other.Type) && + this.DesiredStatus == other.DesiredStatus && + this.Config.Equals(other.Config) && + this.RestartPolicy == other.RestartPolicy && + EnvDictionaryComparer.Equals(this.Env, other.Env); } public override int GetHashCode() { unchecked { - //We are ignoring this here because, we only change the name of the module on Creation. This - //is needed because the name is not part of the body of Json equivalent to IModule, it is on the key of the json. + // We are ignoring this here because, we only change the name of the module on Creation. This + // is needed because the name is not part of the body of Json equivalent to IModule, it is on the key of the json. // ReSharper disable NonReadonlyMemberInGetHashCode - int hashCode = (this.Name != null ? this.Name.GetHashCode() : 0); + int hashCode = this.Name != null ? this.Name.GetHashCode() : 0; // ReSharper restore NonReadonlyMemberInGetHashCode hashCode = (hashCode * 397) ^ (this.Version != null ? this.Version.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.Type != null ? this.Type.GetHashCode() : 0); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerPlatformInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerPlatformInfo.cs index 7a2e4f2852e..8428d04e9ec 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerPlatformInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerPlatformInfo.cs @@ -24,13 +24,17 @@ public DockerPlatformInfo(string operatingSystemType, string architecture, strin [JsonProperty("version")] public string Version { get; } + public static bool operator ==(DockerPlatformInfo info1, DockerPlatformInfo info2) => EqualityComparer.Default.Equals(info1, info2); + + public static bool operator !=(DockerPlatformInfo info1, DockerPlatformInfo info2) => !(info1 == info2); + public override bool Equals(object obj) => this.Equals(obj as DockerPlatformInfo); public bool Equals(DockerPlatformInfo other) => - other != null && - this.OperatingSystemType == other.OperatingSystemType && - this.Architecture == other.Architecture && - this.Version == other.Version; + other != null && + this.OperatingSystemType == other.OperatingSystemType && + this.Architecture == other.Architecture && + this.Version == other.Version; public override int GetHashCode() { @@ -40,9 +44,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Version); return hashCode; } - - public static bool operator ==(DockerPlatformInfo info1, DockerPlatformInfo info2) => EqualityComparer.Default.Equals(info1, info2); - - public static bool operator !=(DockerPlatformInfo info1, DockerPlatformInfo info2) => !(info1 == info2); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedConfig.cs index 6df02fe9d47..6870f0fed8b 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedConfig.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedConfig.cs @@ -8,16 +8,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker using Microsoft.Azure.Devices.Edge.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - using CoreConstants = Core.Constants; + using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; [JsonConverter(typeof(DockerReportedConfigJsonConverter))] public class DockerReportedConfig : DockerConfig, IEquatable { public static DockerReportedConfig Unknown = new DockerReportedConfig(CoreConstants.Unknown, string.Empty, string.Empty); - [JsonProperty(PropertyName = "imageHash")] - public string ImageHash { get; } - [JsonConstructor] public DockerReportedConfig(string image, string createOptions, string imageHash) : base(image, createOptions) @@ -31,6 +28,9 @@ public DockerReportedConfig(string image, CreateContainerParameters createOption this.ImageHash = imageHash ?? string.Empty; } + [JsonProperty(PropertyName = "imageHash")] + public string ImageHash { get; } + public override bool Equals(object obj) => this.Equals(obj as DockerReportedConfig); public bool Equals(DockerReportedConfig other) @@ -39,6 +39,7 @@ public bool Equals(DockerReportedConfig other) { return false; } + if (ReferenceEquals(this, other)) { return true; diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedRuntimeInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedRuntimeInfo.cs index 9348288f8e2..58a07dc6c50 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedRuntimeInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedRuntimeInfo.cs @@ -18,12 +18,18 @@ public DockerReportedRuntimeInfo(string type, DockerRuntimeConfig config, Docker [JsonProperty("platform")] public DockerPlatformInfo Platform { get; } + public static bool operator ==(DockerReportedRuntimeInfo info1, DockerReportedRuntimeInfo info2) => + EqualityComparer.Default.Equals(info1, info2); + + public static bool operator !=(DockerReportedRuntimeInfo info1, DockerReportedRuntimeInfo info2) => + !(info1 == info2); + public override bool Equals(object obj) => this.Equals(obj as DockerReportedRuntimeInfo); public bool Equals(DockerReportedRuntimeInfo other) => - other != null && - base.Equals(other) && - EqualityComparer.Default.Equals(this.Platform, other.Platform); + other != null && + base.Equals(other) && + EqualityComparer.Default.Equals(this.Platform, other.Platform); public override int GetHashCode() { @@ -32,11 +38,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Platform); return hashCode; } - - public static bool operator ==(DockerReportedRuntimeInfo info1, DockerReportedRuntimeInfo info2) => - EqualityComparer.Default.Equals(info1, info2); - - public static bool operator !=(DockerReportedRuntimeInfo info1, DockerReportedRuntimeInfo info2) => - !(info1 == info2); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedUnknownRuntimeInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedUnknownRuntimeInfo.cs index ba340eb41c0..b8aadd7ea92 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedUnknownRuntimeInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerReportedUnknownRuntimeInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util; using Newtonsoft.Json; - using CoreConstants = Core.Constants; + using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; public class DockerReportedUnknownRuntimeInfo : IRuntimeInfo, IEquatable { @@ -21,11 +21,17 @@ public DockerReportedUnknownRuntimeInfo(DockerPlatformInfo platform) public string Type => CoreConstants.Unknown; + public static bool operator ==(DockerReportedUnknownRuntimeInfo info1, DockerReportedUnknownRuntimeInfo info2) => + EqualityComparer.Default.Equals(info1, info2); + + public static bool operator !=(DockerReportedUnknownRuntimeInfo info1, DockerReportedUnknownRuntimeInfo info2) => + !(info1 == info2); + public override bool Equals(object obj) => this.Equals(obj as DockerReportedUnknownRuntimeInfo); public bool Equals(DockerReportedUnknownRuntimeInfo other) => - other != null && - EqualityComparer.Default.Equals(this.Platform, other.Platform); + other != null && + EqualityComparer.Default.Equals(this.Platform, other.Platform); public bool Equals(IRuntimeInfo other) => this.Equals(other as DockerReportedUnknownRuntimeInfo); @@ -36,11 +42,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Platform); return hashCode; } - - public static bool operator ==(DockerReportedUnknownRuntimeInfo info1, DockerReportedUnknownRuntimeInfo info2) => - EqualityComparer.Default.Equals(info1, info2); - - public static bool operator !=(DockerReportedUnknownRuntimeInfo info1, DockerReportedUnknownRuntimeInfo info2) => - !(info1 == info2); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeConfig.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeConfig.cs index 793a2e5ae8b..442279f599d 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeConfig.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeConfig.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker using System; using System.Collections.Generic; using System.Collections.Immutable; - using Microsoft.Azure.Devices.Edge.Util; using Newtonsoft.Json; public class DockerRuntimeConfig : IEquatable @@ -31,11 +30,15 @@ public DockerRuntimeConfig(string minDockerVersion, IDictionary RegistryCredentials { get; } + public static bool operator ==(DockerRuntimeConfig config1, DockerRuntimeConfig config2) => EqualityComparer.Default.Equals(config1, config2); + + public static bool operator !=(DockerRuntimeConfig config1, DockerRuntimeConfig config2) => !(config1 == config2); + public override bool Equals(object obj) => this.Equals(obj as DockerRuntimeConfig); public bool Equals(DockerRuntimeConfig other) => other != null && this.MinDockerVersion == other.MinDockerVersion && this.LoggingOptions == other.LoggingOptions && - EqualityComparer>.Default.Equals(RegistryCredentials, other.RegistryCredentials); + EqualityComparer>.Default.Equals(this.RegistryCredentials, other.RegistryCredentials); public override int GetHashCode() { @@ -45,9 +48,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(this.RegistryCredentials); return hashCode; } - - public static bool operator ==(DockerRuntimeConfig config1, DockerRuntimeConfig config2) => EqualityComparer.Default.Equals(config1, config2); - - public static bool operator !=(DockerRuntimeConfig config1, DockerRuntimeConfig config2) => !(config1 == config2); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeInfo.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeInfo.cs index 15ded8d3afe..5c71d1148be 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeInfo.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeInfo.cs @@ -22,13 +22,23 @@ public DockerRuntimeInfo(string type, DockerRuntimeConfig config) [JsonProperty("settings")] public DockerRuntimeConfig Config { get; } + public static bool operator ==(DockerRuntimeInfo info1, DockerRuntimeInfo info2) + { + return EqualityComparer.Default.Equals(info1, info2); + } + + public static bool operator !=(DockerRuntimeInfo info1, DockerRuntimeInfo info2) + { + return !(info1 == info2); + } + public override bool Equals(object obj) => this.Equals(obj as DockerRuntimeInfo); public bool Equals(IRuntimeInfo other) => this.Equals(other as DockerRuntimeInfo); public bool Equals(DockerRuntimeInfo other) => other != null && this.Type == other.Type && - EqualityComparer.Default.Equals(this.Config, other.Config); + EqualityComparer.Default.Equals(this.Config, other.Config); public override int GetHashCode() { @@ -37,15 +47,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Config); return hashCode; } - - public static bool operator ==(DockerRuntimeInfo info1, DockerRuntimeInfo info2) - { - return EqualityComparer.Default.Equals(info1, info2); - } - - public static bool operator !=(DockerRuntimeInfo info1, DockerRuntimeInfo info2) - { - return !(info1 == info2); - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeModule.cs index c15009fa399..eef376d35db 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerRuntimeModule.cs @@ -10,12 +10,20 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker public class DockerRuntimeModule : DockerModule, IRuntimeModule { public DockerRuntimeModule( - string name, string version, ModuleStatus desiredStatus, - RestartPolicy restartPolicy, DockerConfig config, int exitCode, - string statusDescription, DateTime lastStartTime, - DateTime lastExitTime, int restartCount, DateTime lastRestartTime, - ModuleStatus runtimeStatus, ConfigurationInfo configuration, IDictionary env - ) + string name, + string version, + ModuleStatus desiredStatus, + RestartPolicy restartPolicy, + DockerConfig config, + int exitCode, + string statusDescription, + DateTime lastStartTime, + DateTime lastExitTime, + int restartCount, + DateTime lastRestartTime, + ModuleStatus runtimeStatus, + ConfigurationInfo configuration, + IDictionary env) : base(name, version, desiredStatus, restartPolicy, config, configuration, env) { this.ExitCode = exitCode; @@ -29,16 +37,36 @@ public DockerRuntimeModule( [JsonConstructor] DockerRuntimeModule( - string name, string version, string type, ModuleStatus status, - RestartPolicy restartPolicy, DockerConfig config, int? exitCode, - string statusDescription, DateTime lastStartTimeUtc, - DateTime lastExitTimeUtc, int restartCount, - DateTime lastRestartTimeUtc, ModuleStatus runtimeStatus, - ConfigurationInfo configurationInfo, IDictionary env - ) - : this(name, version, status, restartPolicy, config, exitCode ?? 0, - statusDescription, lastStartTimeUtc, lastExitTimeUtc, - restartCount, lastRestartTimeUtc, runtimeStatus, configurationInfo, env) + string name, + string version, + string type, + ModuleStatus status, + RestartPolicy restartPolicy, + DockerConfig config, + int? exitCode, + string statusDescription, + DateTime lastStartTimeUtc, + DateTime lastExitTimeUtc, + int restartCount, + DateTime lastRestartTimeUtc, + ModuleStatus runtimeStatus, + ConfigurationInfo configurationInfo, + IDictionary env) + : this( + name, + version, + status, + restartPolicy, + config, + exitCode ?? 0, + statusDescription, + lastStartTimeUtc, + lastExitTimeUtc, + restartCount, + lastRestartTimeUtc, + runtimeStatus, + configurationInfo, + env) { Preconditions.CheckArgument(type?.Equals("docker") ?? false); } @@ -84,13 +112,14 @@ public override bool Equals(IModule other) if (other is IRuntimeModule reportedOther) { return this.ExitCode == reportedOther.ExitCode && - string.Equals(this.StatusDescription, reportedOther.StatusDescription) && - this.LastStartTimeUtc == reportedOther.LastStartTimeUtc && - this.LastExitTimeUtc == reportedOther.LastExitTimeUtc && - this.RestartCount == reportedOther.RestartCount && - this.LastRestartTimeUtc == reportedOther.LastRestartTimeUtc && - this.RuntimeStatus == reportedOther.RuntimeStatus; + string.Equals(this.StatusDescription, reportedOther.StatusDescription) && + this.LastStartTimeUtc == reportedOther.LastStartTimeUtc && + this.LastExitTimeUtc == reportedOther.LastExitTimeUtc && + this.RestartCount == reportedOther.RestartCount && + this.LastRestartTimeUtc == reportedOther.LastRestartTimeUtc && + this.RuntimeStatus == reportedOther.RuntimeStatus; } + return true; } } @@ -112,9 +141,19 @@ public override int GetHashCode() } public virtual IModule WithRuntimeStatus(ModuleStatus newStatus) => new DockerRuntimeModule( - this.Name, this.Version, this.DesiredStatus, this.RestartPolicy, - this.Config, this.ExitCode, this.StatusDescription, - this.LastStartTimeUtc, this.LastExitTimeUtc, this.RestartCount, - this.LastRestartTimeUtc, newStatus, this.ConfigurationInfo, this.Env); + this.Name, + this.Version, + this.DesiredStatus, + this.RestartPolicy, + this.Config, + this.ExitCode, + this.StatusDescription, + this.LastStartTimeUtc, + this.LastExitTimeUtc, + this.RestartCount, + this.LastRestartTimeUtc, + newStatus, + this.ConfigurationInfo, + this.Env); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerUtil.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerUtil.cs index 05c37914d76..35746ceed36 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerUtil.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/DockerUtil.cs @@ -13,32 +13,32 @@ public static class DockerUtil /// Grammar /// /// reference := name [ ":" tag ] [ "@" digest ] - /// name := [domain '/'] path-component ['/' path-component]* - /// domain := domain-component ['.' domain-component]* [':' port-number] - /// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ - /// port-number := /[0-9]+/ - /// path-component := alpha-numeric [separator alpha-numeric]* + /// name := [domain '/'] path-component ['/' path-component]* + /// domain := domain-component ['.' domain-component]* [':' port-number] + /// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ + /// port-number := /[0-9]+/ + /// path-component := alpha-numeric [separator alpha-numeric]* /// alpha-numeric := /[a-z0-9]+/ - /// separator := /[_.]|__|[-]*/ + /// separator := /[_.]|__|[-]*/ /// - /// tag := /[\w][\w.-]{0,127}/ + /// tag := /[\w][\w.-]{0,127}/ /// - /// digest := digest-algorithm ":" digest-hex - /// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* - /// digest-algorithm-separator := /[+.-_]/ - /// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ - /// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value + /// digest := digest-algorithm ":" digest-hex + /// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* + /// digest-algorithm-separator := /[+.-_]/ + /// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ + /// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value /// - /// identifier := /[a-f0-9]{64}/ - /// short-identifier := /[a-f0-9]{6,64}/ + /// identifier := /[a-f0-9]{64}/ + /// short-identifier := /[a-f0-9]{6,64}/ /// /// tl;dr if there is more than one path-component, and the first component contains a '.' or ':' then /// it is a registry address. /// For more information: https://github.com/docker/distribution/blob/master/reference/reference.go /// - /// - /// - /// + /// docker image tag + /// docker domain + /// true if it is a valid docker image tag; otherwise false. public static bool TryParseDomainFromImage(string image, out string domain) { domain = string.Empty; diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerModule.cs index 3b16b462ec1..e3e4bd561e3 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerModule.cs @@ -31,7 +31,7 @@ public override int GetHashCode() unchecked { // ReSharper disable once NonReadonlyMemberInGetHashCode - int hashCode = (this.Name != null ? this.Name.GetHashCode() : 0); + int hashCode = this.Name != null ? this.Name.GetHashCode() : 0; // ReSharper restore NonReadonlyMemberInGetHashCode hashCode = (hashCode * 397) ^ (this.Version != null ? this.Version.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.Type != null ? this.Type.GetHashCode() : 0); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerRuntimeModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerRuntimeModule.cs index 1e9b9c1c49b..34fb991b2ca 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerRuntimeModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeAgentDockerRuntimeModule.cs @@ -15,13 +15,25 @@ public EdgeAgentDockerRuntimeModule( int exitCode, string statusDescription, DateTime lastStartTimeUtc, - DateTime lastExitTime, + DateTime lastExitTime, ConfigurationInfo configuration, IDictionary env, string version = "") - : base(Core.Constants.EdgeAgentModuleName, version, ModuleStatus.Running, RestartPolicy.Always, config, - exitCode, statusDescription, lastStartTimeUtc, lastExitTime, - 0, DateTime.MinValue, runtimeStatus, configuration, env) + : base( + Core.Constants.EdgeAgentModuleName, + version, + ModuleStatus.Running, + RestartPolicy.Always, + config, + exitCode, + statusDescription, + lastStartTimeUtc, + lastExitTime, + 0, + DateTime.MinValue, + runtimeStatus, + configuration, + env) { // You maybe wondering why we are setting this here again even though // the base class does this assignment. This is due to a behavior @@ -52,7 +64,14 @@ public EdgeAgentDockerRuntimeModule( public override DateTime LastRestartTimeUtc { get; } public override IModule WithRuntimeStatus(ModuleStatus newStatus) => new EdgeAgentDockerRuntimeModule( - (DockerReportedConfig)this.Config, newStatus, this.ExitCode, this.StatusDescription, this.LastStartTimeUtc, - this.LastExitTimeUtc, this.ConfigurationInfo, this.Env, this.Version); + (DockerReportedConfig)this.Config, + newStatus, + this.ExitCode, + this.StatusDescription, + this.LastStartTimeUtc, + this.LastExitTimeUtc, + this.ConfigurationInfo, + this.Env, + this.Version); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerModule.cs index b9eab483e62..d9eddfa071c 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerModule.cs @@ -9,25 +9,30 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker public class EdgeHubDockerModule : DockerModule, IEdgeHubModule { + [JsonConstructor] + public EdgeHubDockerModule( + string type, + ModuleStatus status, + RestartPolicy restartPolicy, + DockerConfig settings, + ConfigurationInfo configuration, + IDictionary env, + string version = "") + : base(Core.Constants.EdgeHubModuleName, version, status, restartPolicy, settings, configuration, env) + { + Preconditions.CheckArgument(type?.Equals("docker") ?? false); + this.DesiredStatus = Preconditions.CheckIsDefined(status); + this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); + } + [JsonProperty(Required = Required.Always, PropertyName = "status")] public override ModuleStatus DesiredStatus { get; } [JsonProperty( PropertyName = "restartPolicy", Required = Required.DisallowNull, - DefaultValueHandling = DefaultValueHandling.Populate - )] + DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue(RestartPolicy.Always)] public override RestartPolicy RestartPolicy { get; } - - [JsonConstructor] - public EdgeHubDockerModule(string type, ModuleStatus status, RestartPolicy restartPolicy, - DockerConfig settings, ConfigurationInfo configuration, IDictionary env, string version = "") - : base(Core.Constants.EdgeHubModuleName, version, status, restartPolicy, settings, configuration, env) - { - Preconditions.CheckArgument(type?.Equals("docker") ?? false); - this.DesiredStatus = Preconditions.CheckIsDefined(status); - this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerRuntimeModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerRuntimeModule.cs index 774b4f3be32..887d6fdeeff 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerRuntimeModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/EdgeHubDockerRuntimeModule.cs @@ -11,15 +11,34 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker public class EdgeHubDockerRuntimeModule : DockerRuntimeModule, IEdgeHubModule { public EdgeHubDockerRuntimeModule( - ModuleStatus desiredStatus, RestartPolicy restartPolicy, DockerConfig config, int exitCode, - string statusDescription, DateTime lastStartTime, - DateTime lastExitTime, int restartCount, DateTime lastRestartTime, - ModuleStatus runtimeStatus, ConfigurationInfo configuration, - IDictionary env, string version = "" - ) - : base(Core.Constants.EdgeHubModuleName, version, desiredStatus, restartPolicy, config, - exitCode, statusDescription, lastStartTime, lastExitTime, - restartCount, lastRestartTime, runtimeStatus, configuration, env) + ModuleStatus desiredStatus, + RestartPolicy restartPolicy, + DockerConfig config, + int exitCode, + string statusDescription, + DateTime lastStartTime, + DateTime lastExitTime, + int restartCount, + DateTime lastRestartTime, + ModuleStatus runtimeStatus, + ConfigurationInfo configuration, + IDictionary env, + string version = "") + : base( + Core.Constants.EdgeHubModuleName, + version, + desiredStatus, + restartPolicy, + config, + exitCode, + statusDescription, + lastStartTime, + lastExitTime, + restartCount, + lastRestartTime, + runtimeStatus, + configuration, + env) { // You maybe wondering why we are setting this here again even though // the base class does this assignment. This is due to a behavior @@ -33,16 +52,34 @@ public EdgeHubDockerRuntimeModule( [JsonConstructor] EdgeHubDockerRuntimeModule( - string type, ModuleStatus status, - RestartPolicy restartPolicy, DockerConfig config, int? exitCode, - string statusDescription, DateTime lastStartTimeUtc, - DateTime lastExitTimeUtc, int restartCount, - DateTime lastRestartTimeUtc, ModuleStatus runtimeStatus, - ConfigurationInfo configurationInfo, IDictionary env, string version = "" - ) - : this(status, restartPolicy, config, exitCode ?? 0, - statusDescription, lastStartTimeUtc, lastExitTimeUtc, - restartCount, lastRestartTimeUtc, runtimeStatus, configurationInfo, env, version) + string type, + ModuleStatus status, + RestartPolicy restartPolicy, + DockerConfig config, + int? exitCode, + string statusDescription, + DateTime lastStartTimeUtc, + DateTime lastExitTimeUtc, + int restartCount, + DateTime lastRestartTimeUtc, + ModuleStatus runtimeStatus, + ConfigurationInfo configurationInfo, + IDictionary env, + string version = "") + : this( + status, + restartPolicy, + config, + exitCode ?? 0, + statusDescription, + lastStartTimeUtc, + lastExitTimeUtc, + restartCount, + lastRestartTimeUtc, + runtimeStatus, + configurationInfo, + env, + version) { Preconditions.CheckArgument(type?.Equals("docker") ?? false); } @@ -50,8 +87,7 @@ public EdgeHubDockerRuntimeModule( [JsonProperty( PropertyName = "restartPolicy", Required = Required.DisallowNull, - DefaultValueHandling = DefaultValueHandling.Populate - )] + DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue(RestartPolicy.Always)] public override RestartPolicy RestartPolicy { get; } @@ -59,8 +95,17 @@ public EdgeHubDockerRuntimeModule( public override string Version { get; } public override IModule WithRuntimeStatus(ModuleStatus newStatus) => new EdgeHubDockerRuntimeModule( - this.DesiredStatus, this.RestartPolicy, this.Config, this.ExitCode, this.StatusDescription, - this.LastStartTimeUtc, this.LastExitTimeUtc, this.RestartCount, this.LastRestartTimeUtc, - newStatus, this.ConfigurationInfo, this.Env); + this.DesiredStatus, + this.RestartPolicy, + this.Config, + this.ExitCode, + this.StatusDescription, + this.LastStartTimeUtc, + this.LastExitTimeUtc, + this.RestartCount, + this.LastRestartTimeUtc, + newStatus, + this.ConfigurationInfo, + this.Env); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Microsoft.Azure.Devices.Edge.Agent.Docker.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Microsoft.Azure.Devices.Edge.Agent.Docker.csproj index 7d6a826b7f4..80eb4fe9219 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Microsoft.Azure.Devices.Edge.Agent.Docker.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/Microsoft.Azure.Devices.Edge.Agent.Docker.csproj @@ -31,4 +31,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/RuntimeInfoProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/RuntimeInfoProvider.cs index 612011a482f..d567b3f3e30 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/RuntimeInfoProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/RuntimeInfoProvider.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; - using CoreConstants = Core.Constants; + using CoreConstants = Microsoft.Azure.Devices.Edge.Agent.Core.Constants; public class RuntimeInfoProvider : IRuntimeInfoProvider { @@ -35,7 +35,7 @@ public class RuntimeInfoProvider : IRuntimeInfoProvider this.version = string.IsNullOrWhiteSpace(version) ? CoreConstants.Unknown : version; } - public async static Task CreateAsync(IDockerClient client) + public static async Task CreateAsync(IDockerClient client) { Preconditions.CheckNotNull(client, nameof(client)); @@ -64,18 +64,34 @@ public async Task> GetModules(CancellationToken c return modules; } - async Task> GetEdgeAgentContainerAsync() + public Task GetSystemInfo() => Task.FromResult(new SystemInfo(this.operatingSystemType, this.architecture, this.version)); + + internal static ModuleRuntimeInfo InspectResponseToModule(ContainerInspectResponse inspectResponse) { - try - { - ContainerInspectResponse response = await this.client.Containers.InspectContainerAsync(CoreConstants.EdgeAgentModuleName); - return Option.Some(response); - } - catch (DockerContainerNotFoundException ex) - { - Events.EdgeAgentContainerNotFound(ex); - return Option.None(); - } + // Get the following runtime state: + // - name + // - exit code + // - exit status description + // - last start time + // - last exit time + // - image hash + ( + string name, + int exitCode, + string statusDescription, + DateTime lastStartTime, + DateTime lastExitTime, + string imageHash + ) = ExtractModuleRuntimeState(inspectResponse); + + var dockerConfig = new DockerReportedConfig(string.Empty, string.Empty, imageHash); + + // Figure out module stats and runtime status + ModuleStatus runtimeStatus = ToRuntimeStatus(inspectResponse.State); + + var reportedConfig = new DockerReportedConfig(string.Empty, string.Empty, imageHash); + var moduleRuntimeInfo = new ModuleRuntimeInfo(name, "docker", runtimeStatus, statusDescription, exitCode, Option.Some(lastStartTime), Option.Some(lastExitTime), reportedConfig); + return moduleRuntimeInfo; } static ( @@ -85,8 +101,8 @@ async Task> GetEdgeAgentContainerAsync() DateTime lastStartTime, DateTime lastExitTime, string imageHash - ) - ExtractModuleRuntimeState(ContainerInspectResponse inspected) + ) + ExtractModuleRuntimeState(ContainerInspectResponse inspected) { string name = inspected.Name?.Substring(1) ?? CoreConstants.Unknown; int exitCode = (inspected?.State != null) ? (int)inspected.State.ExitCode : 0; @@ -110,34 +126,6 @@ string imageHash return (name, exitCode, statusDescription, lastStartTime, lastExitTime, inspected?.Image); } - internal static ModuleRuntimeInfo InspectResponseToModule(ContainerInspectResponse inspectResponse) - { - // Get the following runtime state: - // - name - // - exit code - // - exit status description - // - last start time - // - last exit time - // - image hash - ( - string name, - int exitCode, - string statusDescription, - DateTime lastStartTime, - DateTime lastExitTime, - string imageHash - ) = ExtractModuleRuntimeState(inspectResponse); - - var dockerConfig = new DockerReportedConfig(string.Empty, string.Empty, imageHash); - - // Figure out module stats and runtime status - ModuleStatus runtimeStatus = ToRuntimeStatus(inspectResponse.State); - - var reportedConfig = new DockerReportedConfig(string.Empty, string.Empty, imageHash); - var moduleRuntimeInfo = new ModuleRuntimeInfo(name, "docker", runtimeStatus, statusDescription, exitCode, Option.Some(lastStartTime), Option.Some(lastExitTime), reportedConfig); - return moduleRuntimeInfo; - } - static ModuleStatus ToRuntimeStatus(ContainerState containerState) { ModuleStatus status; @@ -172,12 +160,24 @@ static ModuleStatus ToRuntimeStatus(ContainerState containerState) return status; } - public Task GetSystemInfo() => Task.FromResult(new SystemInfo(this.operatingSystemType, this.architecture, this.version)); + async Task> GetEdgeAgentContainerAsync() + { + try + { + ContainerInspectResponse response = await this.client.Containers.InspectContainerAsync(CoreConstants.EdgeAgentModuleName); + return Option.Some(response); + } + catch (DockerContainerNotFoundException ex) + { + Events.EdgeAgentContainerNotFound(ex); + return Option.None(); + } + } static class Events { - static readonly ILogger Log = Util.Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.DockerEnvironment; + static readonly ILogger Log = Logger.Factory.CreateLogger(); static bool edgeAgentContainerNotFoundReported; enum EventIds diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/CreateCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/CreateCommand.cs index 552aa60dcef..44344d99cb2 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/CreateCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/CreateCommand.cs @@ -17,11 +17,13 @@ public class CreateCommand : ICommand { readonly CreateContainerParameters createContainerParameters; readonly IDockerClient client; + static readonly Dictionary EdgeHubPortBinding = new Dictionary { - {"8883/tcp", new PortBinding {HostPort="8883" } }, - {"443/tcp", new PortBinding {HostPort="443" } } + { "8883/tcp", new PortBinding { HostPort = "8883" } }, + { "443/tcp", new PortBinding { HostPort = "443" } } }; + readonly Lazy id; public CreateCommand(IDockerClient client, CreateContainerParameters createContainerParameters) @@ -41,8 +43,7 @@ public static async Task BuildAsync( IModuleIdentity identity, DockerLoggingConfig defaultDockerLoggerConfig, IConfigSource configSource, - bool buildForEdgeHub - ) + bool buildForEdgeHub) { // Validate parameters Preconditions.CheckNotNull(client, nameof(client)); @@ -85,6 +86,26 @@ bool buildForEdgeHub public Task UndoAsync(CancellationToken token) => TaskEx.Done; + internal static void InjectPortBindings(CreateContainerParameters createContainerParameters, bool injectForEdgeHub) + { + if (injectForEdgeHub) + { + createContainerParameters.HostConfig = createContainerParameters.HostConfig ?? new HostConfig(); + createContainerParameters.HostConfig.PortBindings = createContainerParameters.HostConfig.PortBindings ?? new Dictionary>(); + + foreach (KeyValuePair binding in EdgeHubPortBinding) + { + IList current = createContainerParameters.HostConfig.PortBindings.GetOrElse(binding.Key, () => new List()); + if (!current.Any(p => p.HostPort.Equals(binding.Value.HostPort, StringComparison.OrdinalIgnoreCase))) + { + current.Add(binding.Value); + } + + createContainerParameters.HostConfig.PortBindings[binding.Key] = current; + } + } + } + static void InjectConfig(CreateContainerParameters createContainerParameters, IModuleIdentity identity, bool injectForEdgeHub, IConfigSource configSource) { var envVars = new List(); @@ -113,25 +134,6 @@ static void InjectConfig(CreateContainerParameters createContainerParameters, IM InjectEnvVars(createContainerParameters, envVars); } - internal static void InjectPortBindings(CreateContainerParameters createContainerParameters, bool injectForEdgeHub) - { - if (injectForEdgeHub) - { - createContainerParameters.HostConfig = createContainerParameters.HostConfig ?? new HostConfig(); - createContainerParameters.HostConfig.PortBindings = createContainerParameters.HostConfig.PortBindings ?? new Dictionary>(); - - foreach (KeyValuePair binding in EdgeHubPortBinding) - { - IList current = createContainerParameters.HostConfig.PortBindings.GetOrElse(binding.Key, () => new List()); - if (!current.Any(p => p.HostPort.Equals(binding.Value.HostPort, StringComparison.OrdinalIgnoreCase))) - { - current.Add(binding.Value); - } - createContainerParameters.HostConfig.PortBindings[binding.Key] = current; - } - } - } - static void InjectLoggerConfig(CreateContainerParameters createContainerParameters, DockerLoggingConfig defaultDockerLoggerConfig, Option sourceLoggingOptions) { createContainerParameters.HostConfig = createContainerParameters.HostConfig ?? new HostConfig(); @@ -139,21 +141,23 @@ static void InjectLoggerConfig(CreateContainerParameters createContainerParamete Option sourceOptions; try { - sourceOptions = sourceLoggingOptions.Filter(l => !string.IsNullOrEmpty(l)).Map(l => - JsonConvert.DeserializeObject(l)); + sourceOptions = sourceLoggingOptions.Filter(l => !string.IsNullOrEmpty(l)).Map( + l => + JsonConvert.DeserializeObject(l)); } catch { sourceOptions = Option.None(); } - if (createContainerParameters.HostConfig.LogConfig == null || (string.IsNullOrWhiteSpace(createContainerParameters.HostConfig.LogConfig.Type))) + if (createContainerParameters.HostConfig.LogConfig == null || string.IsNullOrWhiteSpace(createContainerParameters.HostConfig.LogConfig.Type)) { - createContainerParameters.HostConfig.LogConfig = sourceOptions.GetOrElse(new LogConfig - { - Type = defaultDockerLoggerConfig.Type, - Config = defaultDockerLoggerConfig.Config - }); + createContainerParameters.HostConfig.LogConfig = sourceOptions.GetOrElse( + new LogConfig + { + Type = defaultDockerLoggerConfig.Type, + Config = defaultDockerLoggerConfig.Config + }); } } @@ -235,8 +239,7 @@ static void InjectCerts(CreateContainerParameters createContainerParameters, ICo InjectVolume( createContainerParameters, configSource.Configuration.GetValue(Constants.EdgeHubVolumeNameKey, string.Empty), - configSource.Configuration.GetValue(Constants.EdgeHubVolumePathKey, string.Empty) - ); + configSource.Configuration.GetValue(Constants.EdgeHubVolumePathKey, string.Empty)); } else { @@ -252,8 +255,7 @@ static void InjectCerts(CreateContainerParameters createContainerParameters, ICo InjectVolume( createContainerParameters, configSource.Configuration.GetValue(Constants.EdgeModuleVolumeNameKey, string.Empty), - configSource.Configuration.GetValue(Constants.EdgeModuleVolumePathKey, string.Empty) - ); + configSource.Configuration.GetValue(Constants.EdgeModuleVolumePathKey, string.Empty)); } InjectEnvVars(createContainerParameters, varsList); @@ -268,13 +270,13 @@ static void InjectModuleEnvVars( { envVars.Add($"{envVar.Key}={envVar.Value.Value}"); } + InjectEnvVars(createContainerParameters, envVars); } static void InjectEnvVars( CreateContainerParameters createContainerParameters, - IList varsList - ) + IList varsList) { createContainerParameters.Env = createContainerParameters.Env?.RemoveIntersectionKeys(varsList).ToList() ?? new List(); foreach (string envVar in varsList) diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/PullCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/PullCommand.cs index 5553e3f41f0..fcd539f30b0 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/PullCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/PullCommand.cs @@ -3,13 +3,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Commands { using System; using System.Linq; + using System.Net; using System.Threading; using System.Threading.Tasks; using global::Docker.DotNet; using global::Docker.DotNet.Models; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util; - using System.Net; public class PullCommand : ICommand { @@ -39,6 +39,7 @@ public async Task ExecuteAsync(CancellationToken token) image = imageParts[0]; tag = string.Empty; } + var pullParameters = new ImagesCreateParameters { FromImage = image, @@ -47,10 +48,11 @@ public async Task ExecuteAsync(CancellationToken token) try { - await this.client.Images.CreateImageAsync(pullParameters, - this.combinedDockerConfig.AuthConfig.OrDefault(), - new Progress(), - token); + await this.client.Images.CreateImageAsync( + pullParameters, + this.combinedDockerConfig.AuthConfig.OrDefault(), + new Progress(), + token); } catch (DockerApiException ex) { @@ -62,7 +64,8 @@ await this.client.Images.CreateImageAsync(pullParameters, { throw new InternalServerErrorException(image, tag, ex.StatusCode.ToString(), ex); } - //otherwise throw + + // otherwise throw throw; } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/RemoveCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/RemoveCommand.cs index c54ec279c47..6f63999d2c8 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/RemoveCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Docker/commands/RemoveCommand.cs @@ -13,12 +13,11 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Commands public class RemoveCommand : ICommand { + const string LogLinesToPull = "25"; + const int WaitForLogs = 30000; // ms static readonly ILogger Logger = Util.Logger.Factory.CreateLogger(); readonly IDockerClient client; readonly DockerModule module; - const string LogLinesToPull = "25"; - const int WaitForLogs = 30000; //ms - public RemoveCommand(IDockerClient client, DockerModule module) { this.client = Preconditions.CheckNotNull(client, nameof(client)); @@ -27,6 +26,22 @@ public RemoveCommand(IDockerClient client, DockerModule module) public string Id => $"RemoveCommand({this.module.Name})"; + public async Task ExecuteAsync(CancellationToken token) + { + var parameters = new ContainerRemoveParameters(); + long exitCode = await this.GetModuleExitCode(); + if (exitCode != 0) + { + await this.TailLogsAsync(exitCode); + } + + await this.client.Containers.RemoveContainerAsync(this.module.Name, parameters, token); + } + + public Task UndoAsync(CancellationToken token) => TaskEx.Done; + + public string Show() => $"docker rm {this.module.Name}"; + async Task GetModuleExitCode() { ContainerInspectResponse containerInfo = await this.client.Containers.InspectContainerAsync(this.module.Name); @@ -59,6 +74,7 @@ async Task TailLogsAsync(long exitCode) Logger.LogError($"Last {LogLinesToPull} log lines from container {this.module.Name}:"); firstLine = false; } + string line = await reader.ReadLineAsync(); Logger.LogError(line); } @@ -71,20 +87,5 @@ async Task TailLogsAsync(long exitCode) Logger.LogError($"Unable to get logs from module {this.module.Name} - {ex.Message}"); } } - - public async Task ExecuteAsync(CancellationToken token) - { - var parameters = new ContainerRemoveParameters(); - long exitCode = await this.GetModuleExitCode(); - if (exitCode != 0) - { - await this.TailLogsAsync(exitCode); - } - await this.client.Containers.RemoveContainerAsync(this.module.Name, parameters, token); - } - - public Task UndoAsync(CancellationToken token) => TaskEx.Done; - - public string Show() => $"docker rm {this.module.Name}"; } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs index daca620c50a..5b1554a1890 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/CombinedEdgeletConfigProvider.cs @@ -16,18 +16,14 @@ public class CombinedEdgeletConfigProvider : CombinedDockerConfigProvider { readonly IConfigSource configSource; - public CombinedEdgeletConfigProvider(IEnumerable authConfigs, + public CombinedEdgeletConfigProvider( + IEnumerable authConfigs, IConfigSource configSource) : base(authConfigs) { this.configSource = Preconditions.CheckNotNull(configSource, nameof(configSource)); } - static CreateContainerParameters CloneOrCreateParams(CreateContainerParameters createOptions) => - createOptions != null - ? JsonConvert.DeserializeObject(JsonConvert.SerializeObject(createOptions)) - : new CreateContainerParameters(); - public override CombinedDockerConfig GetCombinedConfig(IModule module, IRuntimeInfo runtimeInfo) { CombinedDockerConfig combinedConfig = base.GetCombinedConfig(module, runtimeInfo); @@ -40,6 +36,31 @@ public override CombinedDockerConfig GetCombinedConfig(IModule module, IRuntimeI return new CombinedDockerConfig(combinedConfig.Image, createOptions, combinedConfig.AuthConfig); } + static CreateContainerParameters CloneOrCreateParams(CreateContainerParameters createOptions) => + createOptions != null + ? JsonConvert.DeserializeObject(JsonConvert.SerializeObject(createOptions)) + : new CreateContainerParameters(); + + static void SetMountOptions(CreateContainerParameters createOptions, Uri uri) + { + HostConfig hostConfig = createOptions.HostConfig ?? new HostConfig(); + IList binds = hostConfig.Binds ?? new List(); + string path = BindPath(uri); + binds.Add($"{path}:{path}"); + + hostConfig.Binds = binds; + createOptions.HostConfig = hostConfig; + } + + static string BindPath(Uri uri) + { + // On Windows we need to bind to the parent folder. We can't bind + // directly to the socket file. + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.GetDirectoryName(uri.LocalPath) + : uri.AbsolutePath; + } + void InjectNetworkAliases(IModule module, CreateContainerParameters createOptions) { if (createOptions.NetworkingConfig?.EndpointsConfig == null) @@ -87,25 +108,5 @@ void MountSockets(IModule module, CreateContainerParameters createOptions) SetMountOptions(createOptions, managementUri); } } - - static void SetMountOptions(CreateContainerParameters createOptions, Uri uri) - { - HostConfig hostConfig = createOptions.HostConfig ?? new HostConfig(); - IList binds = hostConfig.Binds ?? new List(); - string path = BindPath(uri); - binds.Add($"{path}:{path}"); - - hostConfig.Binds = binds; - createOptions.HostConfig = hostConfig; - } - - static String BindPath(Uri uri) - { - // On Windows we need to bind to the parent folder. We can't bind - // directly to the socket file. - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.GetDirectoryName(uri.LocalPath) - : uri.AbsolutePath; - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.csproj index 8bfeff9c209..68512157edf 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.csproj @@ -13,4 +13,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/EdgeletCommandFactory.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/EdgeletCommandFactory.cs index ca90c4b78fd..fcabf8e2388 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/EdgeletCommandFactory.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/EdgeletCommandFactory.cs @@ -18,14 +18,15 @@ public EdgeletCommandFactory(IModuleManager moduleManager, IConfigSource configS this.configSource = Preconditions.CheckNotNull(configSource, nameof(configSource)); this.combinedConfigProvider = Preconditions.CheckNotNull(combinedConfigProvider, nameof(combinedConfigProvider)); } - + public Task CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => - Task.FromResult(CreateOrUpdateCommand.BuildCreate( - this.moduleManager, - module.Module, - module.ModuleIdentity, - this.configSource, - this.combinedConfigProvider.GetCombinedConfig(module.Module, runtimeInfo)) as ICommand); + Task.FromResult( + CreateOrUpdateCommand.BuildCreate( + this.moduleManager, + module.Module, + module.ModuleIdentity, + this.configSource, + this.combinedConfigProvider.GetCombinedConfig(module.Module, runtimeInfo)) as ICommand); public Task UpdateAsync(IModule current, IModuleWithIdentity next, IRuntimeInfo runtimeInfo) => this.UpdateAsync(next, runtimeInfo, false); @@ -33,15 +34,6 @@ public Task UpdateAsync(IModule current, IModuleWithIdentity next, IRu public Task UpdateEdgeAgentAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) => this.UpdateAsync(module, runtimeInfo, true); - Task UpdateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo, bool start) => - Task.FromResult(CreateOrUpdateCommand.BuildUpdate( - this.moduleManager, - module.Module, - module.ModuleIdentity, - this.configSource, - this.combinedConfigProvider.GetCombinedConfig(module.Module, runtimeInfo), - start) as ICommand); - public Task RemoveAsync(IModule module) => Task.FromResult(new RemoveCommand(this.moduleManager, module) as ICommand); public Task StartAsync(IModule module) => Task.FromResult(new StartCommand(this.moduleManager, module) as ICommand); @@ -51,5 +43,15 @@ Task UpdateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo, public Task RestartAsync(IModule module) => Task.FromResult(new RestartCommand(this.moduleManager, module) as ICommand); public Task WrapAsync(ICommand command) => Task.FromResult(command); + + Task UpdateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo, bool start) => + Task.FromResult( + CreateOrUpdateCommand.BuildUpdate( + this.moduleManager, + module.Module, + module.ModuleIdentity, + this.configSource, + this.combinedConfigProvider.GetCombinedConfig(module.Module, runtimeInfo), + start) as ICommand); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/Microsoft.Azure.Devices.Edge.Agent.Edgelet.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/Microsoft.Azure.Devices.Edge.Agent.Edgelet.csproj index e87a43b7d0a..a8edc60b332 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/Microsoft.Azure.Devices.Edge.Agent.Edgelet.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/Microsoft.Azure.Devices.Edge.Agent.Edgelet.csproj @@ -27,4 +27,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleIdentityLifecycleManager.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleIdentityLifecycleManager.cs index b47c3fa5036..bdc88b32b05 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleIdentityLifecycleManager.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleIdentityLifecycleManager.cs @@ -65,8 +65,9 @@ async Task> GetModuleIdentitiesAsy // Remove identities which exist in iotedged but don't exist in the deployment anymore. We exclude however, identities that // aren't managed by Edge since these have been created by some out-of-band process and Edge doesn't "own" the identity. - IEnumerable removeIdentities = removedModuleNames.Where(m => identities.ContainsKey(m) && - Constants.ModuleIdentityEdgeManagedByValue.Equals(identities[m].ManagedBy, StringComparison.OrdinalIgnoreCase)); + IEnumerable removeIdentities = removedModuleNames.Where( + m => identities.ContainsKey(m) && + Constants.ModuleIdentityEdgeManagedByValue.Equals(identities[m].ManagedBy, StringComparison.OrdinalIgnoreCase)); // First remove identities (so that we don't go over the IoTHub limit). await Task.WhenAll(removeIdentities.Select(i => this.identityManager.DeleteIdentityAsync(i))); @@ -83,6 +84,7 @@ async Task> GetModuleIdentitiesAsy { moduleIdentities.Add(this.GetModuleIdentity(identities[Constants.EdgeAgentModuleIdentityName])); } + return moduleIdentities.ToImmutableDictionary(m => ModuleIdentityHelper.GetModuleName(m.ModuleId)); } @@ -91,8 +93,8 @@ IModuleIdentity GetModuleIdentity(Identity identity) => static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.ModuleIdentityLifecycleManager; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleManagementHttpClient.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleManagementHttpClient.cs index 302090430ed..0386ec2bca5 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleManagementHttpClient.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/ModuleManagementHttpClient.cs @@ -16,8 +16,10 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet public class ModuleManagementHttpClient : IModuleManager, IIdentityManager { static readonly ITransientErrorDetectionStrategy TransientErrorDetectionStrategy = new ErrorDetectionStrategy(); + static readonly RetryStrategy TransientRetryStrategy = new ExponentialBackoff(retryCount: 3, minBackoff: TimeSpan.FromSeconds(2), maxBackoff: TimeSpan.FromSeconds(30), deltaBackoff: TimeSpan.FromSeconds(3)); + readonly Uri managementUri; public ModuleManagementHttpClient(Uri managementUri) @@ -48,14 +50,15 @@ public async Task UpdateIdentityAsync(string name, string generationId using (HttpClient httpClient = HttpClientHelper.GetHttpClient(this.managementUri)) { var edgeletHttpClient = new EdgeletHttpClient(httpClient) { BaseUrl = HttpClientHelper.GetBaseUrl(this.managementUri) }; - Identity identity = await this.Execute(() => edgeletHttpClient.UpdateIdentityAsync( - Constants.EdgeletManagementApiVersion, - name, - new UpdateIdentity - { - GenerationId = generationId, - ManagedBy = managedBy - }), + Identity identity = await this.Execute( + () => edgeletHttpClient.UpdateIdentityAsync( + Constants.EdgeletManagementApiVersion, + name, + new UpdateIdentity + { + GenerationId = generationId, + ManagedBy = managedBy + }), $"Update identity for {name} with generation ID {generationId}"); return identity; } @@ -169,12 +172,21 @@ public async Task UpdateAndStartModuleAsync(ModuleSpec moduleSpec) } } + static Task ExecuteWithRetry(Func> func, Action onRetry) + { + var transientRetryPolicy = new RetryPolicy(TransientErrorDetectionStrategy, TransientRetryStrategy); + transientRetryPolicy.Retrying += (_, args) => onRetry(args); + return transientRetryPolicy.ExecuteAsync(func); + } + Task Execute(Func func, string operation) => - this.Execute(async () => - { - await func(); - return 1; - }, operation); + this.Execute( + async () => + { + await func(); + return 1; + }, + operation); async Task Execute(Func> func, string operation) { @@ -190,8 +202,8 @@ async Task Execute(Func> func, string operation) switch (ex) { case SwaggerException errorResponseException: - throw new EdgeletCommunicationException($"Error calling {operation}: {errorResponseException.Result?.Message ?? string.Empty}", errorResponseException.StatusCode); - + throw new EdgeletCommunicationException($"Error calling {operation}: {errorResponseException.Result?.Message ?? string.Empty}", errorResponseException.StatusCode); + case SwaggerException swaggerException: if (swaggerException.StatusCode < 400) { @@ -202,29 +214,23 @@ async Task Execute(Func> func, string operation) { throw new EdgeletCommunicationException($"Error calling {operation}: {swaggerException.Response ?? string.Empty}", swaggerException.StatusCode); } + default: throw; } } } - static Task ExecuteWithRetry(Func> func, Action onRetry) - { - var transientRetryPolicy = new RetryPolicy(TransientErrorDetectionStrategy, TransientRetryStrategy); - transientRetryPolicy.Retrying += (_, args) => onRetry(args); - return transientRetryPolicy.ExecuteAsync(func); - } - class ErrorDetectionStrategy : ITransientErrorDetectionStrategy { public bool IsTransient(Exception ex) => ex is SwaggerException se - && se.StatusCode >= 500; + && se.StatusCode >= 500; } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.ModuleManagementHttpClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/RuntimeInfoProvider.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/RuntimeInfoProvider.cs index e05db061f4c..5f8e9456255 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/RuntimeInfoProvider.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/RuntimeInfoProvider.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet using Microsoft.Azure.Devices.Edge.Agent.Edgelet.GeneratedCode; using Microsoft.Azure.Devices.Edge.Util; using Newtonsoft.Json.Linq; + using SystemInfo = Microsoft.Azure.Devices.Edge.Agent.Core.SystemInfo; public class RuntimeInfoProvider : IRuntimeInfoProvider { @@ -27,11 +28,11 @@ public async Task> GetModules(CancellationToken t return modulesRuntimeInfo; } - public async Task GetSystemInfo() + public async Task GetSystemInfo() { GeneratedCode.SystemInfo systemInfo = await this.moduleManager.GetSystemInfoAsync(); - return new Core.SystemInfo(systemInfo.OsType, systemInfo.Architecture, systemInfo.Version); + return new SystemInfo(systemInfo.OsType, systemInfo.Architecture, systemInfo.Version); } internal static ModuleRuntimeInfo GetModuleRuntimeInfo(ModuleDetails moduleDetails) @@ -54,10 +55,18 @@ internal static ModuleRuntimeInfo GetModuleRuntimeInfo(ModuleDetails moduleDe { throw new InvalidOperationException($"Module config is of type {moduleDetails.Config.Settings.GetType()}. Expected type JObject"); } + var config = jobject.ToObject(); - var moduleRuntimeInfo = new ModuleRuntimeInfo(moduleDetails.Name, moduleDetails.Type, status, - moduleDetails.Status.RuntimeStatus.Description, exitCode, startTime, exitTime, config); + var moduleRuntimeInfo = new ModuleRuntimeInfo( + moduleDetails.Name, + moduleDetails.Type, + status, + moduleDetails.Status.RuntimeStatus.Description, + exitCode, + startTime, + exitTime, + config); return moduleRuntimeInfo; } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/commands/CreateOrUpdateCommand.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/commands/CreateOrUpdateCommand.cs index 9bcbcba554d..a45e1ebcc51 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/commands/CreateOrUpdateCommand.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Edgelet/commands/CreateOrUpdateCommand.cs @@ -28,6 +28,15 @@ public class CreateOrUpdateCommand : ICommand this.operation = operation; } + enum Operation + { + Create, + Update, + UpdateAndStart + } + + public string Id => this.id.Value; + public static CreateOrUpdateCommand BuildCreate( IModuleManager moduleManager, IModule module, @@ -45,22 +54,6 @@ public static CreateOrUpdateCommand BuildUpdate( bool start) => Build(moduleManager, module, identity, configSource, settings, start ? Operation.UpdateAndStart : Operation.Update); - static CreateOrUpdateCommand Build(IModuleManager moduleManager, IModule module, IModuleIdentity identity, - IConfigSource configSource, object settings, Operation operation) - { - Preconditions.CheckNotNull(moduleManager, nameof(moduleManager)); - Preconditions.CheckNotNull(module, nameof(module)); - Preconditions.CheckNotNull(identity, nameof(identity)); - Preconditions.CheckNotNull(configSource, nameof(configSource)); - Preconditions.CheckNotNull(settings, nameof(settings)); - - IEnumerable envVars = GetEnvVars(module.Env, identity, configSource); - ModuleSpec moduleSpec = BuildModuleSpec(module, envVars, settings); - return new CreateOrUpdateCommand(moduleManager, moduleSpec, operation); - } - - public string Id => this.id.Value; - public Task ExecuteAsync(CancellationToken token) { switch (this.operation) @@ -209,11 +202,23 @@ internal static IEnumerable GetEnvVars(IDictionary modul return envVars; } - enum Operation + static CreateOrUpdateCommand Build( + IModuleManager moduleManager, + IModule module, + IModuleIdentity identity, + IConfigSource configSource, + object settings, + Operation operation) { - Create, - Update, - UpdateAndStart + Preconditions.CheckNotNull(moduleManager, nameof(moduleManager)); + Preconditions.CheckNotNull(module, nameof(module)); + Preconditions.CheckNotNull(identity, nameof(identity)); + Preconditions.CheckNotNull(configSource, nameof(configSource)); + Preconditions.CheckNotNull(settings, nameof(settings)); + + IEnumerable envVars = GetEnvVars(module.Env, identity, configSource); + ModuleSpec moduleSpec = BuildModuleSpec(module, envVars, settings); + return new CreateOrUpdateCommand(moduleManager, moduleSpec, operation); } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/AgentState.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/AgentState.cs index 5c71e832096..471caa62b9a 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/AgentState.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/AgentState.cs @@ -11,9 +11,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub public class AgentState : IEquatable { - static readonly DictionaryComparer StringModuleDictionaryComparer = new DictionaryComparer(); - public static AgentState Empty = new AgentState(); + static readonly DictionaryComparer StringModuleDictionaryComparer = new DictionaryComparer(); [JsonConstructor] public AgentState( @@ -23,8 +22,7 @@ public AgentState( SystemModules systemModules = null, IDictionary modules = null, string schemaVersion = "", - VersionInfo version = null - ) + VersionInfo version = null) { this.SchemaVersion = schemaVersion ?? string.Empty; this.Version = version ?? VersionInfo.Empty; @@ -57,6 +55,16 @@ public AgentState( [JsonProperty(PropertyName = "modules")] public IImmutableDictionary Modules { get; } + public static bool operator ==(AgentState state1, AgentState state2) + { + return EqualityComparer.Default.Equals(state1, state2); + } + + public static bool operator !=(AgentState state1, AgentState state2) + { + return !(state1 == state2); + } + public AgentState Clone() => new AgentState( this.LastDesiredVersion, this.LastDesiredStatus.Clone(), @@ -64,20 +72,19 @@ public AgentState( this.SystemModules.Clone(), this.Modules.ToImmutableDictionary(), this.SchemaVersion, - this.Version - ); + this.Version); public override bool Equals(object obj) => this.Equals(obj as AgentState); public bool Equals(AgentState other) => - other != null && - this.LastDesiredVersion == other.LastDesiredVersion && - this.SchemaVersion == other.SchemaVersion && - EqualityComparer.Default.Equals(this.Version, other.Version) && - EqualityComparer.Default.Equals(this.LastDesiredStatus, other.LastDesiredStatus) && - EqualityComparer.Default.Equals(this.RuntimeInfo, other.RuntimeInfo) && - this.SystemModules.Equals(other.SystemModules) && - StringModuleDictionaryComparer.Equals(this.Modules.ToImmutableDictionary(), other.Modules.ToImmutableDictionary()); + other != null && + this.LastDesiredVersion == other.LastDesiredVersion && + this.SchemaVersion == other.SchemaVersion && + EqualityComparer.Default.Equals(this.Version, other.Version) && + EqualityComparer.Default.Equals(this.LastDesiredStatus, other.LastDesiredStatus) && + EqualityComparer.Default.Equals(this.RuntimeInfo, other.RuntimeInfo) && + this.SystemModules.Equals(other.SystemModules) && + StringModuleDictionaryComparer.Equals(this.Modules.ToImmutableDictionary(), other.Modules.ToImmutableDictionary()); public override int GetHashCode() { @@ -91,15 +98,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + StringModuleDictionaryComparer.GetHashCode(this.Modules.ToImmutableDictionary()); return hashCode; } - - public static bool operator ==(AgentState state1, AgentState state2) - { - return EqualityComparer.Default.Equals(state1, state2); - } - - public static bool operator !=(AgentState state1, AgentState state2) - { - return !(state1 == state2); - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/EdgeAgentConnection.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/EdgeAgentConnection.cs index eb0e193d937..56f73053c11 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/EdgeAgentConnection.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/EdgeAgentConnection.cs @@ -13,12 +13,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; + using ExponentialBackoff = Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling.ExponentialBackoff; public class EdgeAgentConnection : IEdgeAgentConnection { - static readonly TimeSpan DefaultConfigRefreshFrequency = TimeSpan.FromHours(1); - internal static readonly Version ExpectedSchemaVersion = new Version("1.0"); const string PingMethodName = "ping"; + internal static readonly Version ExpectedSchemaVersion = new Version("1.0"); + static readonly TimeSpan DefaultConfigRefreshFrequency = TimeSpan.FromHours(1); static readonly Task PingMethodResponse = Task.FromResult(new MethodResponse(200)); static readonly TimeSpan DeviceClientInitializationWaitTime = TimeSpan.FromSeconds(5); readonly AsyncLock twinLock = new AsyncLock(); @@ -33,21 +34,27 @@ public class EdgeAgentConnection : IEdgeAgentConnection Option deploymentConfigInfo; static readonly ITransientErrorDetectionStrategy AllButFatalErrorDetectionStrategy = new DelegateErrorDetectionStrategy(ex => ex.IsFatal() == false); + static readonly RetryStrategy TransientRetryStrategy = - new Util.TransientFaultHandling.ExponentialBackoff(5, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); + new ExponentialBackoff(5, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); - public EdgeAgentConnection(IModuleClientProvider moduleClientProvider, + public EdgeAgentConnection( + IModuleClientProvider moduleClientProvider, ISerde desiredPropertiesSerDe) : this(moduleClientProvider, desiredPropertiesSerDe, TransientRetryStrategy, DefaultConfigRefreshFrequency) - { } + { + } - public EdgeAgentConnection(IModuleClientProvider moduleClientProvider, + public EdgeAgentConnection( + IModuleClientProvider moduleClientProvider, ISerde desiredPropertiesSerDe, TimeSpan configRefreshFrequency) : this(moduleClientProvider, desiredPropertiesSerDe, TransientRetryStrategy, configRefreshFrequency) - { } + { + } - internal EdgeAgentConnection(IModuleClientProvider moduleClientProvider, + internal EdgeAgentConnection( + IModuleClientProvider moduleClientProvider, ISerde desiredPropertiesSerDe, RetryStrategy retryStrategy, TimeSpan refreshConfigFrequency) @@ -64,6 +71,46 @@ internal EdgeAgentConnection(IModuleClientProvider moduleClientProvider, Events.TwinRefreshInit(refreshConfigFrequency); } + public Option ReportedProperties => this.reportedProperties; + + public async Task> GetDeploymentConfigInfoAsync() + { + await this.WaitForDeviceClientInitialization(); + return this.deploymentConfigInfo; + } + + public void Dispose() + { + this.deviceClient.ForEach(d => d.Dispose()); + this.refreshTimer?.Dispose(); + } + + public async Task UpdateReportedPropertiesAsync(TwinCollection patch) + { + if (await this.WaitForDeviceClientInitialization()) + { + await this.deviceClient.ForEachAsync(d => d.UpdateReportedPropertiesAsync(patch)); + } + } + + internal static void ValidateSchemaVersion(string schemaVersion) + { + if (string.IsNullOrWhiteSpace(schemaVersion) || !Version.TryParse(schemaVersion, out Version version)) + { + throw new InvalidSchemaVersionException($"Invalid desired properties schema version {schemaVersion ?? string.Empty}"); + } + + if (ExpectedSchemaVersion.Major != version.Major) + { + throw new InvalidSchemaVersionException($"Desired properties schema version {schemaVersion} is not compatible with the expected version {ExpectedSchemaVersion}"); + } + + if (ExpectedSchemaVersion.Minor != version.Minor) + { + Events.MismatchedMinorVersions(version, ExpectedSchemaVersion); + } + } + async void RefreshTimerElapsed() => await this.RefreshTwinAsync(); void ResetRefreshTimer() @@ -218,53 +265,13 @@ Task UpdateDeploymentConfig() return Task.CompletedTask; } - internal static void ValidateSchemaVersion(string schemaVersion) - { - if (string.IsNullOrWhiteSpace(schemaVersion) || !Version.TryParse(schemaVersion, out Version version)) - { - throw new InvalidSchemaVersionException($"Invalid desired properties schema version {schemaVersion ?? string.Empty}"); - } - - if (ExpectedSchemaVersion.Major != version.Major) - { - throw new InvalidSchemaVersionException($"Desired properties schema version {schemaVersion} is not compatible with the expected version {ExpectedSchemaVersion}"); - } - - if (ExpectedSchemaVersion.Minor != version.Minor) - { - Events.MismatchedMinorVersions(version, ExpectedSchemaVersion); - } - } - - public async Task> GetDeploymentConfigInfoAsync() - { - await this.WaitForDeviceClientInitialization(); - return this.deploymentConfigInfo; - } - async Task WaitForDeviceClientInitialization() => await Task.WhenAny(this.initTask, Task.Delay(DeviceClientInitializationWaitTime)) == this.initTask; - public Option ReportedProperties => this.reportedProperties; - - public void Dispose() - { - this.deviceClient.ForEach(d => d.Dispose()); - this.refreshTimer?.Dispose(); - } - - public async Task UpdateReportedPropertiesAsync(TwinCollection patch) - { - if (await this.WaitForDeviceClientInitialization()) - { - await this.deviceClient.ForEachAsync(d => d.UpdateReportedPropertiesAsync(patch)); - } - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.EdgeAgentConnection; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -294,6 +301,13 @@ public static void ConnectionStatusChanged(ConnectionStatus status, ConnectionSt Log.LogDebug((int)EventIds.ConnectionStatusChanged, $"Connection status changed to {status} with reason {reason}"); } + public static void MismatchedMinorVersions(Version receivedVersion, Version expectedVersion) + { + Log.LogWarning( + (int)EventIds.MismatchedSchemaVersion, + $"Desired properties schema version {receivedVersion} does not match expected schema version {expectedVersion}. Some settings may not be supported."); + } + internal static void DesiredPropertiesUpdated() { Log.LogDebug((int)EventIds.DesiredPropertiesUpdated, "Edge agent desired properties updated callback invoked."); @@ -308,6 +322,7 @@ internal static void ConnectionStatusChangedHandlingError(Exception ex) { Log.LogWarning((int)EventIds.ErrorHandlingConnectionChangeEvent, ex, "Edge agent connection error handing connection change callback."); } + internal static void TwinRefreshInit(TimeSpan interval) { Log.LogDebug((int)EventIds.TwinRefreshInit, "Initialize twin refresh with interval '{0:c}'", interval); @@ -347,12 +362,6 @@ internal static void RetryingGetTwin(RetryingEventArgs args) { Log.LogDebug((int)EventIds.RetryingGetTwin, $"Edge agent is retrying GetTwinAsync. Attempt #{args.CurrentRetryCount}. Last error: {args.LastException?.Message}"); } - - public static void MismatchedMinorVersions(Version receivedVersion, Version expectedVersion) - { - Log.LogWarning((int)EventIds.MismatchedSchemaVersion, - $"Desired properties schema version {receivedVersion} does not match expected schema version {expectedVersion}. Some settings may not be supported."); - } } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/IEdgeAgentConnection.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/IEdgeAgentConnection.cs index 16152124b13..613504bac7b 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/IEdgeAgentConnection.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/IEdgeAgentConnection.cs @@ -9,10 +9,10 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub public interface IEdgeAgentConnection : IDisposable { - Task> GetDeploymentConfigInfoAsync(); - Option ReportedProperties { get; } + Task> GetDeploymentConfigInfoAsync(); + Task UpdateReportedPropertiesAsync(TwinCollection patch); } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/Microsoft.Azure.Devices.Edge.Agent.IoTHub.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/Microsoft.Azure.Devices.Edge.Agent.IoTHub.csproj index 8caf25eb064..c7e9aa4c785 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/Microsoft.Azure.Devices.Edge.Agent.IoTHub.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/Microsoft.Azure.Devices.Edge.Agent.IoTHub.csproj @@ -29,4 +29,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleClient.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleClient.cs index 44d80315f4b..84db4a79443 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleClient.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleClient.cs @@ -11,13 +11,16 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; + using ExponentialBackoff = Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling.ExponentialBackoff; public class ModuleClient : IModuleClient { + const uint DeviceClientTimeout = 30000; // ms static readonly ITransientErrorDetectionStrategy TransientErrorDetectionStrategy = new ErrorDetectionStrategy(); + static readonly RetryStrategy TransientRetryStrategy = - new Util.TransientFaultHandling.ExponentialBackoff(int.MaxValue, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); - const uint DeviceClientTimeout = 30000; // ms + new ExponentialBackoff(int.MaxValue, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); + static readonly IDictionary UpstreamProtocolTransportTypeMap = new Dictionary { [UpstreamProtocol.Amqp] = TransportType.Amqp_Tcp_Only, @@ -33,13 +36,48 @@ public class ModuleClient : IModuleClient this.deviceClient = deviceClient; } - public static Task Create(Option connectionString, + public static Task Create( + Option connectionString, Option upstreamProtocol, ConnectionStatusChangesHandler statusChangedHandler, Func initialize, Option productInfo) => Create(upstreamProtocol, initialize, t => CreateAndOpenDeviceClient(t, connectionString, statusChangedHandler, productInfo)); + public Task SetDesiredPropertyUpdateCallbackAsync(DesiredPropertyUpdateCallback onDesiredPropertyChanged) => + this.deviceClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertyChanged, null); + + public Task SetMethodHandlerAsync(string methodName, MethodCallback callback) => + this.deviceClient.SetMethodHandlerAsync(methodName, callback, null); + + public void Dispose() => this.deviceClient.Dispose(); + + public Task GetTwinAsync() => this.deviceClient.GetTwinAsync(); + + public Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties) => this.deviceClient.UpdateReportedPropertiesAsync(reportedProperties); + + internal static Task CreateDeviceClientForUpstreamProtocol( + Option upstreamProtocol, + Func> deviceClientCreator) + => upstreamProtocol + .Map(u => deviceClientCreator(UpstreamProtocolTransportTypeMap[u])) + .GetOrElse( + async () => + { + // The device SDK doesn't appear to be falling back to WebSocket from TCP, + // so we'll do it explicitly until we can get the SDK sorted out. + Try result = await Fallback.ExecuteAsync( + () => deviceClientCreator(TransportType.Amqp_Tcp_Only), + () => deviceClientCreator(TransportType.Amqp_WebSocket_Only)); + if (!result.Success) + { + Events.DeviceConnectionError(result.Exception); + throw result.Exception; + } + + return result.Value; + }); + static async Task Create(Option upstreamProtocol, Func initialize, Func> deviceClientCreator) { try @@ -67,28 +105,8 @@ static async Task Create(Option upstreamProtoco } } - internal static Task CreateDeviceClientForUpstreamProtocol( - Option upstreamProtocol, - Func> deviceClientCreator) - => upstreamProtocol - .Map(u => deviceClientCreator(UpstreamProtocolTransportTypeMap[u])) - .GetOrElse( - async () => - { - // The device SDK doesn't appear to be falling back to WebSocket from TCP, - // so we'll do it explicitly until we can get the SDK sorted out. - Try result = await Fallback.ExecuteAsync( - () => deviceClientCreator(TransportType.Amqp_Tcp_Only), - () => deviceClientCreator(TransportType.Amqp_WebSocket_Only)); - if (!result.Success) - { - Events.DeviceConnectionError(result.Exception); - throw result.Exception; - } - return result.Value; - }); - - static async Task CreateAndOpenDeviceClient(TransportType transport, + static async Task CreateAndOpenDeviceClient( + TransportType transport, Option connectionString, ConnectionStatusChangesHandler statusChangedHandler, Option productInfo) @@ -126,25 +144,13 @@ class ErrorDetectionStrategy : ITransientErrorDetectionStrategy typeof(UnauthorizedException) }; - public bool IsTransient(Exception ex) => !(NonTransientExceptions.Contains(ex.GetType())); + public bool IsTransient(Exception ex) => !NonTransientExceptions.Contains(ex.GetType()); } - public Task SetDesiredPropertyUpdateCallbackAsync(DesiredPropertyUpdateCallback onDesiredPropertyChanged) => - this.deviceClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertyChanged, null); - - public Task SetMethodHandlerAsync(string methodName, MethodCallback callback) => - this.deviceClient.SetMethodHandlerAsync(methodName, callback, null); - - public void Dispose() => this.deviceClient.Dispose(); - - public Task GetTwinAsync() => this.deviceClient.GetTwinAsync(); - - public Task UpdateReportedPropertiesAsync(TwinCollection reportedProperties) => this.deviceClient.UpdateReportedPropertiesAsync(reportedProperties); - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.ModuleClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -178,7 +184,8 @@ public static void DeviceConnectionError(Exception ex) public static void RetryingDeviceClientConnection(RetryingEventArgs args) { - Log.LogDebug((int)EventIds.RetryingDeviceClientConnection, + Log.LogDebug( + (int)EventIds.RetryingDeviceClientConnection, $"Retrying connection to IoT Hub. Current retry count {args.CurrentRetryCount}."); } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleIdentityLifecycleManager.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleIdentityLifecycleManager.cs index de630666449..2f5dd8efffc 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleIdentityLifecycleManager.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ModuleIdentityLifecycleManager.cs @@ -17,7 +17,8 @@ public class ModuleIdentityLifecycleManager : IModuleIdentityLifecycleManager readonly string deviceId; readonly string gatewayHostName; - public ModuleIdentityLifecycleManager(IServiceClient serviceClient, + public ModuleIdentityLifecycleManager( + IServiceClient serviceClient, string iothubHostName, string deviceId, string gatewayHostName) @@ -45,7 +46,6 @@ public ModuleIdentityLifecycleManager(IServiceClient serviceClient, // $edgeAgent and $edgeHub twin when it creates the Edge Device, so their twins // are always available for stamping with either a single deployment or at-scale // deployment. - public async Task> GetModuleIdentitiesAsync(ModuleSet desired, ModuleSet current) { Diff diff = desired.Diff(current); @@ -72,7 +72,6 @@ async Task> GetModuleIdentitiesAsy // and vice versa, to make sure the right values are being used. // TODO - This will fail if the user adds modules with the same module name as a system module - for example a module called // edgeHub. We might have to catch such cases and flag them as error (or handle them in some other way). - IEnumerable updatedModuleIdentites = diff.Updated.Select(m => ModuleIdentityHelper.GetModuleIdentityName(m.Name)); IEnumerable removedModuleIdentites = diff.Removed.Select(m => ModuleIdentityHelper.GetModuleIdentityName(m)); @@ -81,17 +80,18 @@ async Task> GetModuleIdentitiesAsy ImmutableDictionary modulesDict = modules.ToImmutableDictionary(p => p.Id); IEnumerable createIdentities = updatedModuleIdentites.Where(m => !modulesDict.ContainsKey(m)); - IEnumerable removeIdentities = removedModuleIdentites.Where(m => modulesDict.ContainsKey(m) - && string.Equals(modulesDict.GetValueOrDefault(m).ManagedBy, Constants.ModuleIdentityEdgeManagedByValue, StringComparison.OrdinalIgnoreCase)); + IEnumerable removeIdentities = removedModuleIdentites.Where( + m => modulesDict.ContainsKey(m) + && string.Equals(modulesDict.GetValueOrDefault(m).ManagedBy, Constants.ModuleIdentityEdgeManagedByValue, StringComparison.OrdinalIgnoreCase)); // Update any identities that don't have SAS auth type or where the keys are null (this will happen for single device deployments, // where the identities of modules are created, but the auth keys are not set). IEnumerable updateIdentities = modules.Where( - m => m.Authentication == null - || m.Authentication.Type != AuthenticationType.Sas - || m.Authentication.SymmetricKey == null - || (m.Authentication.SymmetricKey.PrimaryKey == null && m.Authentication.SymmetricKey.SecondaryKey == null)) - .Select( + m => m.Authentication == null + || m.Authentication.Type != AuthenticationType.Sas + || m.Authentication.SymmetricKey == null + || (m.Authentication.SymmetricKey.PrimaryKey == null && m.Authentication.SymmetricKey.SecondaryKey == null)) + .Select( m => { m.Authentication = new AuthenticationMechanism @@ -104,11 +104,12 @@ async Task> GetModuleIdentitiesAsy List updatedModulesIndentity = (await this.UpdateServiceModulesIdentityAsync(removeIdentities, createIdentities, updateIdentities)).ToList(); ImmutableDictionary updatedDict = updatedModulesIndentity.ToImmutableDictionary(p => p.Id); - IEnumerable moduleIdentities = updatedModulesIndentity.Concat(modules.Where(p => !updatedDict.ContainsKey(p.Id))).Select(p => - { - string connectionString = this.GetModuleConnectionString(p); - return new ModuleIdentity(this.iothubHostName, this.gatewayHostName, this.deviceId, p.Id, new ConnectionStringCredentials(connectionString)); - }); + IEnumerable moduleIdentities = updatedModulesIndentity.Concat(modules.Where(p => !updatedDict.ContainsKey(p.Id))).Select( + p => + { + string connectionString = this.GetModuleConnectionString(p); + return new ModuleIdentity(this.iothubHostName, this.gatewayHostName, this.deviceId, p.Id, new ConnectionStringCredentials(connectionString)); + }); return moduleIdentities.ToImmutableDictionary(m => ModuleIdentityHelper.GetModuleName(m.ModuleId)); } @@ -142,8 +143,8 @@ async Task UpdateServiceModulesIdentityAsync(IEnumerable remov static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.ModuleIdentityLifecycleManager; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RetryingServiceClient.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RetryingServiceClient.cs index 75fef180bb5..1c85ac50462 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RetryingServiceClient.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RetryingServiceClient.cs @@ -36,11 +36,13 @@ public Task UpdateModules(IEnumerable modules) => this.ExecuteWithRetry(() => this.underlying.UpdateModules(modules), nameof(this.underlying.UpdateModules)); public Task RemoveModules(IEnumerable identities) => - this.ExecuteWithRetry(async () => - { - await this.underlying.RemoveModules(identities); - return 0; - }, nameof(this.underlying.RemoveModules)); + this.ExecuteWithRetry( + async () => + { + await this.underlying.RemoveModules(identities); + return 0; + }, + nameof(this.underlying.RemoveModules)); Task ExecuteWithRetry(Func> func, string action) { @@ -57,13 +59,13 @@ class DeviceClientRetryStrategy : ITransientErrorDetectionStrategy typeof(UnauthorizedException) }; - public bool IsTransient(Exception ex) => !(NonTransientExceptions.Contains(ex.GetType())); + public bool IsTransient(Exception ex) => !NonTransientExceptions.Contains(ex.GetType()); } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.RetryingServiceClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RuntimePlatform.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RuntimePlatform.cs index ef3acd87792..1bf21361e56 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RuntimePlatform.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/RuntimePlatform.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub { - using Newtonsoft.Json; using System; using System.Collections.Generic; + using Newtonsoft.Json; public class RuntimePlatform : IEquatable { @@ -20,6 +20,16 @@ public RuntimePlatform(string operatingSystem = null, string architecture = null [JsonProperty(PropertyName = "architecture")] public string Architecture { get; } + public static bool operator ==(RuntimePlatform platform1, RuntimePlatform platform2) + { + return EqualityComparer.Default.Equals(platform1, platform2); + } + + public static bool operator !=(RuntimePlatform platform1, RuntimePlatform platform2) + { + return !(platform1 == platform2); + } + public RuntimePlatform Clone() => new RuntimePlatform(this.OperatingSystem, this.Architecture); public override bool Equals(object obj) @@ -41,15 +51,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Architecture); return hashCode; } - - public static bool operator ==(RuntimePlatform platform1, RuntimePlatform platform2) - { - return EqualityComparer.Default.Equals(platform1, platform2); - } - - public static bool operator !=(RuntimePlatform platform1, RuntimePlatform platform2) - { - return !(platform1 == platform2); - } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ServiceClient.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ServiceClient.cs index 020945a9052..449ab1395c3 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ServiceClient.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/ServiceClient.cs @@ -43,10 +43,7 @@ public Task CreateModules(IEnumerable identities) new Module(this.deviceId, moduleId) { ManagedBy = Constants.ModuleIdentityEdgeManagedByValue - } - ) - ) - ); + }))); } public Task UpdateModules(IEnumerable modules) diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/configsources/TwinConfigSource.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/configsources/TwinConfigSource.cs index f352530141b..859cd10ce49 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/configsources/TwinConfigSource.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/configsources/TwinConfigSource.cs @@ -30,8 +30,8 @@ public async Task GetDeploymentConfigInfoAsync() static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.TwinConfigSource; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/reporters/IoTHubReporter.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/reporters/IoTHubReporter.cs index 6fb594be928..c667ff01f82 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/reporters/IoTHubReporter.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.IoTHub/reporters/IoTHubReporter.cs @@ -21,15 +21,14 @@ public class IoTHubReporter : IReporter readonly IEdgeAgentConnection edgeAgentConnection; readonly AsyncLock sync; - Option reportedState; readonly ISerde agentStateSerde; readonly VersionInfo versionInfo; + Option reportedState; public IoTHubReporter( IEdgeAgentConnection edgeAgentConnection, ISerde agentStateSerde, - VersionInfo versionInfo - ) + VersionInfo versionInfo) { this.edgeAgentConnection = Preconditions.CheckNotNull(edgeAgentConnection, nameof(edgeAgentConnection)); this.agentStateSerde = Preconditions.CheckNotNull(agentStateSerde, nameof(agentStateSerde)); @@ -39,6 +38,88 @@ VersionInfo versionInfo this.reportedState = Option.None(); } + public async Task ReportAsync(CancellationToken token, ModuleSet moduleSet, IRuntimeInfo runtimeInfo, long version, Option updatedStatus) + { + Option agentState = Option.None(); + using (await this.sync.LockAsync(token)) + { + try + { + agentState = await this.GetReportedStateAsync(); + // if there is no reported JSON to compare against, then we don't do anything + // because this typically means that we never connected to IoT Hub before and + // we have no connection yet + await agentState.ForEachAsync( + async rs => + { + AgentState currentState = this.BuildCurrentState(moduleSet, runtimeInfo, version > 0 ? version : rs.LastDesiredVersion, updatedStatus.GetOrElse(rs.LastDesiredStatus)); + // diff, prepare patch and report + await this.DiffAndReportAsync(currentState, rs); + }); + } + catch (Exception ex) when (!ex.IsFatal()) + { + Events.BuildStateFailed(ex); + + // something failed during the patch generation process; we do best effort + // error reporting by sending a minimal patch with just the error information + JObject patch = JObject.FromObject( + new + { + lastDesiredVersion = agentState.Map(rs => rs.LastDesiredVersion).GetOrElse(0), + lastDesiredStatus = new DeploymentStatus(DeploymentStatusCode.Failed, ex.Message) + }); + + try + { + await this.edgeAgentConnection.UpdateReportedPropertiesAsync(new TwinCollection(patch.ToString())); + } + catch (Exception ex2) when (!ex2.IsFatal()) + { + Events.UpdateErrorInfoFailed(ex2); + } + } + } + } + + public async Task ReportShutdown(DeploymentStatus status, CancellationToken token) + { + using (await this.sync.LockAsync(token)) + { + Preconditions.CheckNotNull(status, nameof(status)); + Option agentState = await this.GetReportedStateAsync(); + await agentState.ForEachAsync(state => this.ReportShutdownInternal(state, status)); + } + } + + internal async Task DiffAndReportAsync(AgentState currentState, AgentState agentState) + { + try + { + JToken currentJson = JToken.FromObject(currentState); + JToken reportedJson = JToken.FromObject(agentState); + JObject patch = JsonEx.Diff(reportedJson, currentJson); + + if (patch.HasValues) + { + // send reported props + await this.edgeAgentConnection.UpdateReportedPropertiesAsync(new TwinCollection(patch.ToString())); + + // update our cached copy of reported properties + this.SetReported(currentState); + + Events.UpdatedReportedProperties(); + } + } + catch (Exception e) + { + Events.UpdateReportedPropertiesFailed(e); + + // Swallow the exception as the device could be offline. The reported properties will get updated + // during the next reconcile when we have connectivity. + } + } + async Task> GetReportedStateAsync() { try @@ -57,7 +138,6 @@ async Task> GetReportedStateAsync() await deletionPatch.ForEachAsync(patch => this.edgeAgentConnection.UpdateReportedPropertiesAsync(patch)); return Option.Some(AgentState.Empty); } - } Option MakeDeletionPatch(Option reportedProperties) @@ -102,64 +182,17 @@ AgentState BuildCurrentState(ModuleSet moduleSet, IRuntimeInfo runtimeInfo, long userModules = currentModules.RemoveRange(new[] { Constants.EdgeAgentModuleName, Constants.EdgeHubModuleName }).ToImmutableDictionary(); } - var currentState = new AgentState(version, status, runtimeInfo, new SystemModules(edgeAgentModule, edgeHubModule), - userModules, CurrentReportedPropertiesSchemaVersion, this.versionInfo); + var currentState = new AgentState( + version, + status, + runtimeInfo, + new SystemModules(edgeAgentModule, edgeHubModule), + userModules, + CurrentReportedPropertiesSchemaVersion, + this.versionInfo); return currentState; } - public async Task ReportAsync(CancellationToken token, ModuleSet moduleSet, IRuntimeInfo runtimeInfo, long version, Option updatedStatus) - { - Option agentState = Option.None(); - using (await this.sync.LockAsync(token)) - { - try - { - agentState = await this.GetReportedStateAsync(); - // if there is no reported JSON to compare against, then we don't do anything - // because this typically means that we never connected to IoT Hub before and - // we have no connection yet - await agentState.ForEachAsync(async rs => - { - AgentState currentState = this.BuildCurrentState(moduleSet, runtimeInfo, version > 0 ? version : rs.LastDesiredVersion, updatedStatus.GetOrElse(rs.LastDesiredStatus)); - // diff, prepare patch and report - await this.DiffAndReportAsync(currentState, rs); - }); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - Events.BuildStateFailed(ex); - - // something failed during the patch generation process; we do best effort - // error reporting by sending a minimal patch with just the error information - JObject patch = JObject.FromObject(new - { - lastDesiredVersion = agentState.Map(rs => rs.LastDesiredVersion).GetOrElse(0), - lastDesiredStatus = new DeploymentStatus(DeploymentStatusCode.Failed, ex.Message) - }); - - try - { - await this.edgeAgentConnection.UpdateReportedPropertiesAsync(new TwinCollection(patch.ToString())); - } - catch (Exception ex2) when (!ex2.IsFatal()) - { - Events.UpdateErrorInfoFailed(ex2); - } - } - } - } - - public async Task ReportShutdown(DeploymentStatus status, CancellationToken token) - { - using (await this.sync.LockAsync(token)) - { - Preconditions.CheckNotNull(status, nameof(status)); - Option agentState = await this.GetReportedStateAsync(); - await agentState.ForEachAsync(state => this.ReportShutdownInternal(state, status)); - } - } - Task ReportShutdownInternal(AgentState agentState, DeploymentStatus status) { Option edgeAgentModule = agentState.SystemModules.EdgeAgent @@ -178,56 +211,32 @@ Task ReportShutdownInternal(AgentState agentState, DeploymentStatus status) .Where(m => m.Key != Constants.EdgeAgentModuleName) .Where(m => m.Key != Constants.EdgeHubModuleName) .Where(m => m.Value is IRuntimeModule) - .Select(pair => - { - IModule updatedModule = (pair.Value as IRuntimeModule)?.WithRuntimeStatus(ModuleStatus.Unknown) ?? pair.Value; - return new KeyValuePair(pair.Key, updatedModule); - }) + .Select( + pair => + { + IModule updatedModule = (pair.Value as IRuntimeModule)?.WithRuntimeStatus(ModuleStatus.Unknown) ?? pair.Value; + return new KeyValuePair(pair.Key, updatedModule); + }) .ToDictionary(x => x.Key, x => x.Value); var currentState = new AgentState( - agentState.LastDesiredVersion, status, agentState.RuntimeInfo, + agentState.LastDesiredVersion, + status, + agentState.RuntimeInfo, new SystemModules(edgeAgentModule, edgeHubModule), updateUserModules.ToImmutableDictionary(), - agentState.SchemaVersion, this.versionInfo); + agentState.SchemaVersion, + this.versionInfo); return this.DiffAndReportAsync(currentState, agentState); } - - internal async Task DiffAndReportAsync(AgentState currentState, AgentState agentState) - { - try - { - JToken currentJson = JToken.FromObject(currentState); - JToken reportedJson = JToken.FromObject(agentState); - JObject patch = JsonEx.Diff(reportedJson, currentJson); - - if (patch.HasValues) - { - // send reported props - await this.edgeAgentConnection.UpdateReportedPropertiesAsync(new TwinCollection(patch.ToString())); - - // update our cached copy of reported properties - this.SetReported(currentState); - - Events.UpdatedReportedProperties(); - } - } - catch (Exception e) - { - Events.UpdateReportedPropertiesFailed(e); - - // Swallow the exception as the device could be offline. The reported properties will get updated - // during the next reconcile when we have connectivity. - } - } } static class Events { - static readonly ILogger Log = Util.Logger.Factory.CreateLogger(); const int IdStart = AgentEventIds.IoTHubReporter; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj index 941ecce35a4..c3427b45eac 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Microsoft.Azure.Devices.Edge.Agent.Service.csproj @@ -55,4 +55,12 @@ PreserveNewest + + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs index ef1dcb45743..c2fbe902cbd 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/Program.cs @@ -13,14 +13,13 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; - using ILogger = Extensions.Logging.ILogger; public class Program { - static readonly TimeSpan ShutdownWaitPeriod = TimeSpan.FromMinutes(1); const string ConfigFileName = "appsettings_agent.json"; const string EdgeAgentStorageFolder = "edgeAgent"; const string VersionInfoFileName = "versionInfo.json"; + static readonly TimeSpan ShutdownWaitPeriod = TimeSpan.FromMinutes(1); public static int Main() { @@ -53,6 +52,7 @@ public static async Task MainAsync(IConfiguration configuration) { logger.LogInformation($"Version - {versionInfo.ToString(true)}"); } + LogLogo(logger); string mode; @@ -154,7 +154,7 @@ public static async Task MainAsync(IConfiguration configuration) Option agentOption = Option.None(); try - { + { Agent agent = await container.Resolve>(); agentOption = Option.Some(agent); while (!cts.Token.IsCancellationRequested) @@ -167,8 +167,10 @@ public static async Task MainAsync(IConfiguration configuration) { logger.LogWarning(AgentEventIds.Agent, ex, "Agent reconcile concluded with errors."); } + await Task.Delay(TimeSpan.FromSeconds(5), cts.Token); } + logger.LogInformation("Closing module management agent."); returnCode = 0; @@ -188,6 +190,7 @@ public static async Task MainAsync(IConfiguration configuration) await Cleanup(agentOption, logger); completed.Set(); } + handler.ForEach(h => GC.KeepAlive(h)); return returnCode; } @@ -234,7 +237,8 @@ static string GetStoragePath(IConfiguration configuration) static void LogLogo(ILogger logger) { - logger.LogInformation(@" + logger.LogInformation( + @" █████╗ ███████╗██╗ ██╗██████╗ ███████╗ ██╔══██╗╚══███╔╝██║ ██║██╔══██╗██╔════╝ ███████║ ███╔╝ ██║ ██║██████╔╝█████╗ diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/AgentModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/AgentModule.cs index 3896fbbb225..c2f6933c1b3 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/AgentModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/AgentModule.cs @@ -18,6 +18,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules public class AgentModule : Module { + const string DockerType = "docker"; readonly int maxRestartCount; readonly TimeSpan intensiveCareTime; readonly int coolOffTimeUnitInSeconds; @@ -26,7 +27,31 @@ public class AgentModule : Module readonly Option workloadUri; readonly string moduleId; readonly Option moduleGenerationId; - const string DockerType = "docker"; + + public AgentModule(int maxRestartCount, TimeSpan intensiveCareTime, int coolOffTimeUnitInSeconds, bool usePersistentStorage, string storagePath) + : this(maxRestartCount, intensiveCareTime, coolOffTimeUnitInSeconds, usePersistentStorage, storagePath, Option.None(), Constants.EdgeAgentModuleIdentityName, Option.None()) + { + } + + public AgentModule( + int maxRestartCount, + TimeSpan intensiveCareTime, + int coolOffTimeUnitInSeconds, + bool usePersistentStorage, + string storagePath, + Option workloadUri, + string moduleId, + Option moduleGenerationId) + { + this.maxRestartCount = maxRestartCount; + this.intensiveCareTime = intensiveCareTime; + this.coolOffTimeUnitInSeconds = coolOffTimeUnitInSeconds; + this.usePersistentStorage = usePersistentStorage; + this.storagePath = Preconditions.CheckNonWhiteSpace(storagePath, nameof(storagePath)); + this.workloadUri = workloadUri; + this.moduleId = moduleId; + this.moduleGenerationId = moduleGenerationId; + } static Dictionary> DeploymentConfigTypeMapping { @@ -63,64 +88,45 @@ static Dictionary> DeploymentConfigTypeMapping } } - public AgentModule(int maxRestartCount, TimeSpan intensiveCareTime, int coolOffTimeUnitInSeconds, bool usePersistentStorage, string storagePath) - : this(maxRestartCount, intensiveCareTime, coolOffTimeUnitInSeconds, usePersistentStorage, storagePath, Option.None(), Constants.EdgeAgentModuleIdentityName, Option.None()) - { - - } - - public AgentModule(int maxRestartCount, TimeSpan intensiveCareTime, int coolOffTimeUnitInSeconds, - bool usePersistentStorage, string storagePath, Option workloadUri, string moduleId, Option moduleGenerationId) - { - this.maxRestartCount = maxRestartCount; - this.intensiveCareTime = intensiveCareTime; - this.coolOffTimeUnitInSeconds = coolOffTimeUnitInSeconds; - this.usePersistentStorage = usePersistentStorage; - this.storagePath = Preconditions.CheckNonWhiteSpace(storagePath, nameof(storagePath)); - this.workloadUri = workloadUri; - this.moduleId = moduleId; - this.moduleGenerationId = moduleGenerationId; - } - protected override void Load(ContainerBuilder builder) { // ISerde - builder.Register(c => new DiffSerde( - new Dictionary - { - { DockerType, typeof(DockerModule) } - } - )) + builder.Register( + c => new DiffSerde( + new Dictionary + { + { DockerType, typeof(DockerModule) } + })) .As>() .SingleInstance(); // ISerde - builder.Register(c => new ModuleSetSerde( - new Dictionary - { - { DockerType, typeof(DockerModule) } - } - )) + builder.Register( + c => new ModuleSetSerde( + new Dictionary + { + { DockerType, typeof(DockerModule) } + })) .As>() .SingleInstance(); // ISerde builder.Register( - c => - { - ISerde serde = new TypeSpecificSerDe(DeploymentConfigTypeMapping); - return serde; - }) + c => + { + ISerde serde = new TypeSpecificSerDe(DeploymentConfigTypeMapping); + return serde; + }) .As>() .SingleInstance(); // ISerde builder.Register( - c => - { - ISerde serde = new TypeSpecificSerDe(DeploymentConfigTypeMapping); - return serde; - }) + c => + { + ISerde serde = new TypeSpecificSerDe(DeploymentConfigTypeMapping); + return serde; + }) .As>() .SingleInstance(); @@ -137,34 +143,36 @@ protected override void Load(ContainerBuilder builder) // IDbStore builder.Register( - c => - { - var loggerFactory = c.Resolve(); - ILogger logger = loggerFactory.CreateLogger(typeof(AgentModule)); - - if (this.usePersistentStorage) + c => { - // Create partition for mma - var partitionsList = new List { "moduleState", "deploymentConfig" }; - try + var loggerFactory = c.Resolve(); + ILogger logger = loggerFactory.CreateLogger(typeof(AgentModule)); + + if (this.usePersistentStorage) { - IDbStoreProvider dbStoreprovider = DbStoreProvider.Create(c.Resolve(), - this.storagePath, partitionsList); - logger.LogInformation($"Created persistent store at {this.storagePath}"); - return dbStoreprovider; + // Create partition for mma + var partitionsList = new List { "moduleState", "deploymentConfig" }; + try + { + IDbStoreProvider dbStoreprovider = DbStoreProvider.Create( + c.Resolve(), + this.storagePath, + partitionsList); + logger.LogInformation($"Created persistent store at {this.storagePath}"); + return dbStoreprovider; + } + catch (Exception ex) when (!ExceptionEx.IsFatal(ex)) + { + logger.LogError(ex, "Error creating RocksDB store. Falling back to in-memory store."); + return new InMemoryDbStoreProvider(); + } } - catch (Exception ex) when (!ExceptionEx.IsFatal(ex)) + else { - logger.LogError(ex, "Error creating RocksDB store. Falling back to in-memory store."); + logger.LogInformation($"Using in-memory store"); return new InMemoryDbStoreProvider(); } - } - else - { - logger.LogInformation($"Using in-memory store"); - return new InMemoryDbStoreProvider(); - } - }) + }) .As() .SingleInstance(); @@ -189,12 +197,12 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); // IPlanner - builder.Register(async c => new HealthRestartPlanner( - await c.Resolve>(), - c.Resolve>(), - this.intensiveCareTime, - c.Resolve() - ) as IPlanner) + builder.Register( + async c => new HealthRestartPlanner( + await c.Resolve>(), + c.Resolve>(), + this.intensiveCareTime, + c.Resolve()) as IPlanner) .As>() .SingleInstance(); @@ -227,7 +235,7 @@ await c.Resolve>(), // Task builder.Register( - async c => + async c => { var configSource = c.Resolve>(); var environmentProvider = c.Resolve>(); @@ -249,8 +257,8 @@ await c.Resolve>(), deploymentConfigInfoSerde, await encryptionProvider); }) - .As>() - .SingleInstance(); + .As>() + .SingleInstance(); base.Load(builder); } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/DockerModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/DockerModule.cs index 810a092ce03..a4857cc6123 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/DockerModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/DockerModule.cs @@ -5,14 +5,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules using System.Collections.Generic; using System.Threading.Tasks; using Autofac; + using Core; + using Docker; + using Extensions.Logging; using global::Docker.DotNet; using global::Docker.DotNet.Models; - using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Docker; - using Microsoft.Azure.Devices.Edge.Agent.IoTHub; - using Microsoft.Azure.Devices.Edge.Storage; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; + using IoTHub; + using Storage; + using Util; public class DockerModule : Module { @@ -25,8 +25,13 @@ public class DockerModule : Module readonly Option upstreamProtocol; readonly Option productInfo; - public DockerModule(string edgeDeviceConnectionString, string gatewayHostName, Uri dockerHostname, - IEnumerable dockerAuthConfig, Option upstreamProtocol, Option productInfo) + public DockerModule( + string edgeDeviceConnectionString, + string gatewayHostName, + Uri dockerHostname, + IEnumerable dockerAuthConfig, + Option upstreamProtocol, + Option productInfo) { this.edgeDeviceConnectionString = Preconditions.CheckNonWhiteSpace(edgeDeviceConnectionString, nameof(edgeDeviceConnectionString)); this.gatewayHostName = Preconditions.CheckNonWhiteSpace(gatewayHostName, nameof(gatewayHostName)); @@ -52,7 +57,7 @@ protected override void Load(ContainerBuilder builder) .As() .SingleInstance(); - // IModuleIdentityLifecycleManager + // IModuleIdentityLifecycleManager builder.Register(c => new ModuleIdentityLifecycleManager(c.Resolve(), this.iotHubHostName, this.deviceId, this.gatewayHostName)) .As() .SingleInstance(); @@ -83,26 +88,26 @@ protected override void Load(ContainerBuilder builder) // IRuntimeInfoProvider builder.Register( - async c => - { - IRuntimeInfoProvider runtimeInfoProvider = await RuntimeInfoProvider.CreateAsync(c.Resolve()); - return runtimeInfoProvider; - }) + async c => + { + IRuntimeInfoProvider runtimeInfoProvider = await RuntimeInfoProvider.CreateAsync(c.Resolve()); + return runtimeInfoProvider; + }) .As>() .SingleInstance(); // Task builder.Register( - async c => - { - var moduleStateStore = c.Resolve>(); - var restartPolicyManager = c.Resolve(); - IRuntimeInfoProvider runtimeInfoProvider = await c.Resolve>(); - IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager); - return dockerEnvironmentProvider; - }) - .As>() - .SingleInstance(); + async c => + { + var moduleStateStore = c.Resolve>(); + var restartPolicyManager = c.Resolve(); + IRuntimeInfoProvider runtimeInfoProvider = await c.Resolve>(); + IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager); + return dockerEnvironmentProvider; + }) + .As>() + .SingleInstance(); } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/EdgeletModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/EdgeletModule.cs index 289a3f6f762..38f956aef7b 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/EdgeletModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/EdgeletModule.cs @@ -32,8 +32,15 @@ public class EdgeletModule : Module readonly Option upstreamProtocol; readonly Option productInfo; - public EdgeletModule(string iotHubHostname, string gatewayHostName, string deviceId, Uri managementUri, - Uri workloadUri, IEnumerable dockerAuthConfig, Option upstreamProtocol, Option productInfo) + public EdgeletModule( + string iotHubHostname, + string gatewayHostName, + string deviceId, + Uri managementUri, + Uri workloadUri, + IEnumerable dockerAuthConfig, + Option upstreamProtocol, + Option productInfo) { this.iotHubHostName = Preconditions.CheckNonWhiteSpace(iotHubHostname, nameof(iotHubHostname)); this.gatewayHostName = Preconditions.CheckNonWhiteSpace(gatewayHostName, nameof(gatewayHostName)); @@ -66,11 +73,11 @@ protected override void Load(ContainerBuilder builder) // ICombinedConfigProvider builder.Register( - async c => - { - IConfigSource configSource = await c.Resolve>(); - return new CombinedEdgeletConfigProvider(this.dockerAuthConfig, configSource) as ICombinedConfigProvider; - }) + async c => + { + IConfigSource configSource = await c.Resolve>(); + return new CombinedEdgeletConfigProvider(this.dockerAuthConfig, configSource) as ICombinedConfigProvider; + }) .As>>() .SingleInstance(); @@ -94,16 +101,16 @@ protected override void Load(ContainerBuilder builder) // Task builder.Register( - async c => - { - var moduleStateStore = c.Resolve>(); - var restartPolicyManager = c.Resolve(); - var runtimeInfoProvider = c.Resolve(); - IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager); - return dockerEnvironmentProvider; - }) - .As>() - .SingleInstance(); + async c => + { + var moduleStateStore = c.Resolve>(); + var restartPolicyManager = c.Resolve(); + var runtimeInfoProvider = c.Resolve(); + IEnvironmentProvider dockerEnvironmentProvider = await DockerEnvironmentProvider.CreateAsync(runtimeInfoProvider, moduleStateStore, restartPolicyManager); + return dockerEnvironmentProvider; + }) + .As>() + .SingleInstance(); } } } diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/FileConfigSourceModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/FileConfigSourceModule.cs index 213ca90a440..8a28df897e6 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/FileConfigSourceModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/FileConfigSourceModule.cs @@ -15,7 +15,8 @@ public class FileConfigSourceModule : Module readonly string configFilename; readonly IConfiguration configuration; - public FileConfigSourceModule(string configFilename, + public FileConfigSourceModule( + string configFilename, IConfiguration configuration) { this.configFilename = Preconditions.CheckNonWhiteSpace(configFilename, nameof(configFilename)); @@ -26,15 +27,15 @@ protected override void Load(ContainerBuilder builder) { // Task builder.Register( - async c => - { - var serde = c.Resolve>(); - IConfigSource config = await FileConfigSource.Create( - this.configFilename, - this.configuration, - serde); - return config; - }) + async c => + { + var serde = c.Resolve>(); + IConfigSource config = await FileConfigSource.Create( + this.configFilename, + this.configuration, + serde); + return config; + }) .As>() .SingleInstance(); diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/LoggingModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/LoggingModule.cs index 62db2f2e44b..f9e96d0a7a7 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/LoggingModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/LoggingModule.cs @@ -3,8 +3,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules { using System.Collections.Generic; using Autofac; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Agent.Docker; + using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; public class LoggingModule : Module diff --git a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/TwinConfigSourceModule.cs b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/TwinConfigSourceModule.cs index 3abb19c490a..341986a7b35 100644 --- a/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/TwinConfigSourceModule.cs +++ b/edge-agent/src/Microsoft.Azure.Devices.Edge.Agent.Service/modules/TwinConfigSourceModule.cs @@ -17,17 +17,17 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Service.Modules public class TwinConfigSourceModule : Module { - readonly string backupConfigFilePath; const string DockerType = "docker"; + readonly string backupConfigFilePath; readonly IConfiguration configuration; readonly VersionInfo versionInfo; readonly TimeSpan configRefreshFrequency; - public TwinConfigSourceModule(string backupConfigFilePath, + public TwinConfigSourceModule( + string backupConfigFilePath, IConfiguration config, VersionInfo versionInfo, - TimeSpan configRefreshFrequency - ) + TimeSpan configRefreshFrequency) { this.backupConfigFilePath = Preconditions.CheckNonWhiteSpace(backupConfigFilePath, nameof(backupConfigFilePath)); this.configuration = Preconditions.CheckNotNull(config, nameof(config)); @@ -39,13 +39,13 @@ protected override void Load(ContainerBuilder builder) { // IEdgeAgentConnection builder.Register( - c => - { - var serde = c.Resolve>(); - var deviceClientprovider = c.Resolve(); - IEdgeAgentConnection edgeAgentConnection = new EdgeAgentConnection(deviceClientprovider, serde, this.configRefreshFrequency); - return edgeAgentConnection; - }) + c => + { + var serde = c.Resolve>(); + var deviceClientprovider = c.Resolve(); + IEdgeAgentConnection edgeAgentConnection = new EdgeAgentConnection(deviceClientprovider, serde, this.configRefreshFrequency); + return edgeAgentConnection; + }) .As() .SingleInstance(); @@ -101,10 +101,8 @@ protected override void Load(ContainerBuilder builder) return new IoTHubReporter( c.Resolve(), new TypeSpecificSerDe(deserializerTypesMap), - this.versionInfo - ) as IReporter; - } - ) + this.versionInfo) as IReporter; + }) .As() .SingleInstance(); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/AgentTests.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/AgentTests.cs index 12c903bef50..29df1fe88d7 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/AgentTests.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/AgentTests.cs @@ -60,7 +60,7 @@ public async void AgentCreateSuccessWhenDecryptFails() configStore.Setup(cs => cs.Get(It.IsAny())) .ReturnsAsync(Option.Some("encrypted")); encryptionDecryptionProvider.Setup(ep => ep.DecryptAsync(It.IsAny())) - .ThrowsAsync(new IoTEdgedException("failed", 404, "", null, null)); + .ThrowsAsync(new IoTEdgedException("failed", 404, string.Empty, null, null)); Agent agent = await Agent.Create(mockConfigSource.Object, mockPlanner.Object, mockPlanRunner.Object, mockReporter.Object, mockModuleLifecycleManager.Object, mockEnvironmentProvider.Object, configStore.Object, serde, encryptionDecryptionProvider.Object); @@ -84,10 +84,14 @@ public async void ReconcileAsyncOnEmptyPlan() var runtimeInfo = Mock.Of(); var configStore = Mock.Of>(); var encryptionDecryptionProvider = Mock.Of(); - var deploymentConfig = new DeploymentConfig("1.0", runtimeInfo, new SystemModules(null, null), new Dictionary - { - { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } - }); + var deploymentConfig = new DeploymentConfig( + "1.0", + runtimeInfo, + new SystemModules(null, null), + new Dictionary + { + { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } + }); var deploymentConfigInfo = new DeploymentConfigInfo(0, deploymentConfig); ModuleSet desiredModuleSet = deploymentConfig.GetModuleSet(); ModuleSet currentModuleSet = desiredModuleSet; @@ -146,35 +150,12 @@ public async void ReconcileAsyncAbortsWhenConfigSourceThrows() mockPlanRunner.Verify(r => r.ExecuteAsync(1, It.IsAny(), token), Times.Never); } - static IEnumerable GetExceptionsToTest() - { - return new List - { - new object[] - { - new ConfigEmptyException("Empty config"), - DeploymentStatusCode.ConfigEmptyError - }, - new object[] - { - new InvalidSchemaVersionException("Bad schema"), - DeploymentStatusCode.InvalidSchemaVersion - }, - new object[] - { - new ConfigFormatException("Bad config"), - DeploymentStatusCode.ConfigFormatError - } - }; - } - [Theory] [Unit] [MemberData(nameof(GetExceptionsToTest))] public async void ReconcileAsyncAbortsWhenConfigSourceReturnsKnownExceptions( Exception testException, - DeploymentStatusCode statusCode - ) + DeploymentStatusCode statusCode) { // Arrange var mockConfigSource = new Mock(); @@ -257,10 +238,14 @@ public async void ReconcileAsyncAbortsWhenModuleIdentityLifecycleManagerThrows() var mockEnvironmentProvider = Mock.Of(m => m.Create(It.IsAny()) == mockEnvironment.Object); var serde = Mock.Of>(); var encryptionDecryptionProvider = Mock.Of(); - var deploymentConfig = new DeploymentConfig("1.0", Mock.Of(), new SystemModules(null, null), new Dictionary - { - { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } - }); + var deploymentConfig = new DeploymentConfig( + "1.0", + Mock.Of(), + new SystemModules(null, null), + new Dictionary + { + { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } + }); var deploymentConfigInfo = new DeploymentConfigInfo(0, deploymentConfig); ModuleSet desiredModuleSet = deploymentConfig.GetModuleSet(); mockConfigSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()) @@ -298,10 +283,14 @@ public async void ReconcileAsyncReportsFailedWhenEncryptProviderThrows() var runtimeInfo = Mock.Of(); var configStore = Mock.Of>(); var encryptionDecryptionProvider = new Mock(); - var deploymentConfig = new DeploymentConfig("1.0", runtimeInfo, new SystemModules(null, null), new Dictionary - { - { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } - }); + var deploymentConfig = new DeploymentConfig( + "1.0", + runtimeInfo, + new SystemModules(null, null), + new Dictionary + { + { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } + }); var desiredModule = new TestModule("desired", "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null); Option recordKeeper = Option.Some(new TestPlanRecorder()); var deploymentConfigInfo = new DeploymentConfigInfo(0, deploymentConfig); @@ -325,7 +314,7 @@ public async void ReconcileAsyncReportsFailedWhenEncryptProviderThrows() mockPlanner.Setup(pl => pl.PlanAsync(It.Is(ms => ms.Equals(desiredModuleSet)), currentModuleSet, runtimeInfo, ImmutableDictionary.Empty)) .ReturnsAsync(testPlan); encryptionDecryptionProvider.Setup(ep => ep.EncryptAsync(It.IsAny())) - .ThrowsAsync(new IoTEdgedException("failed", 404, "", null, null)); + .ThrowsAsync(new IoTEdgedException("failed", 404, string.Empty, null, null)); var agent = new Agent(mockConfigSource.Object, mockEnvironmentProvider.Object, mockPlanner.Object, mockPlanRunner.Object, mockReporter.Object, mockModuleIdentityLifecycleManager.Object, configStore, DeploymentConfigInfo.Empty, serde, encryptionDecryptionProvider.Object); @@ -349,7 +338,6 @@ public async void ReconcileAsyncOnSetPlan() { new TestRecordType(TestCommandType.TestCreate, desiredModule), new TestRecordType(TestCommandType.TestRemove, currentModule) - }; var commandList = new List { @@ -476,10 +464,14 @@ public async Task ReportShutdownAsyncConfigTest() var mockEnvironmentProvider = Mock.Of(m => m.Create(It.IsAny()) == mockEnvironment.Object); var serde = Mock.Of>(); var encryptionDecryptionProvider = Mock.Of(); - var deploymentConfig = new DeploymentConfig("1.0", Mock.Of(), new SystemModules(null, null), new Dictionary - { - { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } - }); + var deploymentConfig = new DeploymentConfig( + "1.0", + Mock.Of(), + new SystemModules(null, null), + new Dictionary + { + { "mod1", new TestModule("mod1", "1.0", "docker", ModuleStatus.Running, new TestConfig("boo"), RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null) } + }); var deploymentConfigInfo = new DeploymentConfigInfo(0, deploymentConfig); var token = new CancellationToken(); @@ -520,11 +512,12 @@ public async Task HandleShutdownTest() var mockPlanRunner = new Mock(); mockPlanRunner.Setup(m => m.ExecuteAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(async () => - { - await Task.Delay(TimeSpan.FromSeconds(5)); - return true; - }); + .Returns( + async () => + { + await Task.Delay(TimeSpan.FromSeconds(5)); + return true; + }); var mockReporter = new Mock(); mockReporter.Setup( @@ -561,5 +554,27 @@ public async Task HandleShutdownTest() mockPlanRunner.Verify(r => r.ExecuteAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockPlanner.Verify(r => r.CreateShutdownPlanAsync(It.IsAny()), Times.Once); } + + static IEnumerable GetExceptionsToTest() + { + return new List + { + new object[] + { + new ConfigEmptyException("Empty config"), + DeploymentStatusCode.ConfigEmptyError + }, + new object[] + { + new InvalidSchemaVersionException("Bad schema"), + DeploymentStatusCode.InvalidSchemaVersion + }, + new object[] + { + new ConfigFormatException("Bad config"), + DeploymentStatusCode.ConfigFormatError + } + }; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DeploymentConfigInfoTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DeploymentConfigInfoTest.cs index dac30a2c6e9..17db27522f7 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DeploymentConfigInfoTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DeploymentConfigInfoTest.cs @@ -20,7 +20,7 @@ public class DeploymentConfigInfoTest new TestConfig("microsoft/edgeAgent:1.0"), new ConfigurationInfo(), new Dictionary()); - + static readonly IEdgeHubModule TestEdgeHub1 = new TestHubModule( "edgeHub", "docker", @@ -38,7 +38,7 @@ public class DeploymentConfigInfoTest RestartPolicy.Always, new ConfigurationInfo(), new Dictionary()); - + static readonly IModule TestModule1 = new TestModule( "mod1", string.Empty, @@ -58,7 +58,7 @@ public class DeploymentConfigInfoTest RestartPolicy.Always, new ConfigurationInfo(), new Dictionary()); - + static readonly IModule TestModule2 = new TestModule( "mod2", string.Empty, @@ -108,7 +108,7 @@ public class DeploymentConfigInfoTest ["mod1"] = TestModule1 }); - static readonly DeploymentConfigInfo ConfigInfo1 = new DeploymentConfigInfo( 1, Config1); + static readonly DeploymentConfigInfo ConfigInfo1 = new DeploymentConfigInfo(1, Config1); static readonly DeploymentConfigInfo ConfigInfo1_1 = new DeploymentConfigInfo(1, Config1_1); static readonly DeploymentConfigInfo ConfigInfo2 = new DeploymentConfigInfo(1, Config2); static readonly DeploymentConfigInfo ConfigInfo3 = new DeploymentConfigInfo(2, Config1_1); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DiffTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DiffTest.cs index a714d2a6468..02292c62796 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DiffTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/DiffTest.cs @@ -23,7 +23,7 @@ public class DiffTest public void TestEquals() { Diff nonEmptyUpdated = Diff.Create(Module1); - var nonEmptyRemoved = new Diff(ImmutableList.Empty, new List{"module2"}); + var nonEmptyRemoved = new Diff(ImmutableList.Empty, new List { "module2" }); Diff alsoNonEmptyDiff = nonEmptyUpdated; object nonEmptyUpdatedObjectSameReference = nonEmptyUpdated; @@ -36,7 +36,7 @@ public void TestEquals() Assert.False(Diff.Empty.Equals(nonEmptyRemoved)); Assert.False(nonEmptyUpdated.Equals(nonEmptyRemoved)); - Assert.Equal(Module1,Module1A); + Assert.Equal(Module1, Module1A); Assert.True(nonEmptyUpdated.Equals(Diff.Create(Module1A))); } @@ -44,8 +44,8 @@ public void TestEquals() [Unit] public void TestDiffUnordered() { - var diff1 = new Diff(new List{ Module1, Module2}, new List {"mod3", "mod4"}); - var diff2 = new Diff(new List{ Module2, Module1}, new List {"mod4", "mod3"}); + var diff1 = new Diff(new List { Module1, Module2 }, new List { "mod3", "mod4" }); + var diff2 = new Diff(new List { Module2, Module1 }, new List { "mod4", "mod3" }); Assert.Equal(diff1, diff2); } @@ -54,7 +54,7 @@ public void TestDiffUnordered() [Unit] public void TestDiffHash() { - var diff1 = new Diff(new List{ Module1, Module2}, new List {"mod3", "mod4"}); + var diff1 = new Diff(new List { Module1, Module2 }, new List { "mod3", "mod4" }); var diff2 = new Diff(new List { Module2, Module1 }, new List { "mod4", "mod3" }); var diff3 = new Diff(new List { Module1A, Module2 }, new List { "mod3", "mod4" }); var diff4 = new Diff(new List { Module1 }, new List { "mod3" }); @@ -70,20 +70,20 @@ public void TestDiffHash() int hash6 = diff6.GetHashCode(); int hash7 = diff7.GetHashCode(); - Assert.Equal(hash1,hash2); - Assert.Equal(hash1,hash3); - Assert.NotEqual(hash4,hash5); - Assert.NotEqual(hash6,hash7); + Assert.Equal(hash1, hash2); + Assert.Equal(hash1, hash3); + Assert.NotEqual(hash4, hash5); + Assert.NotEqual(hash6, hash7); } [Fact] [Unit] public void TestDiffEmpty() { - //arrange + // arrange var diff1 = new Diff(ImmutableList.Empty, ImmutableList.Empty); - //act - //assert + // act + // assert Assert.True(Diff.Empty.Equals(diff1)); Assert.True(diff1.IsEmpty); } @@ -92,22 +92,23 @@ public void TestDiffEmpty() [Unit] public void TestDiffSerialize() { - //arrange + // arrange var serializerInputTable = new Dictionary() { { "test", typeof(TestModule) } }; var diffSerde = new DiffSerde(serializerInputTable); Diff nonEmptyUpdated = Diff.Create(Module1); - //act - //assert + // act + // assert Assert.Throws(() => diffSerde.Serialize(nonEmptyUpdated)); } + [Fact] [Unit] public void TestDiffDeserialize() { - //"mod1", "version1", "type1", ModuleStatus.Running, Config1 - //Config1 = new TestConfig("image1"); - //arrange + // "mod1", "version1", "type1", ModuleStatus.Running, Config1 + // Config1 = new TestConfig("image1"); + // arrange Diff nonEmptyUpdated = Diff.Create(Module1); string nonEmptyUpdatedJson = "{\"modules\":{\"mod1\":{\"version\":\"version1\",\"type\":\"test\",\"status\":\"running\",\"settings\":{\"image\":\"image1\"},\"restartPolicy\":\"on-unhealthy\",\"configuration\":{\"id\":\"1\",\"version\":\"2\"}}},\"$version\":127}"; string nonEmptyRemovedJson = "{\"modules\":{\"module2\": null },\"$version\":127}"; @@ -119,17 +120,16 @@ public void TestDiffDeserialize() var serializerInputTable = new Dictionary() { { "test", typeof(TestModule) } }; var diffSerde = new DiffSerde(serializerInputTable); - //act + // act Diff nonEmptyUpdatedDeserialized = diffSerde.Deserialize(nonEmptyUpdatedJson); Diff nonEmptyRemovedDeserialized = diffSerde.Deserialize(nonEmptyRemovedJson); - //assert + // assert Assert.Throws(() => diffSerde.Deserialize(nonSupportedTypeModuleJson)); Assert.Throws(() => diffSerde.Deserialize(nonEmptyUpdatedJson)); Assert.Throws(() => diffSerde.Deserialize(noTypeDiffJson)); Assert.True(nonEmptyUpdatedDeserialized.Equals(nonEmptyUpdated)); Assert.True(nonEmptyRemovedDeserialized.Equals(nonEmptyRemoved)); - } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/Microsoft.Azure.Devices.Edge.Agent.Core.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/Microsoft.Azure.Devices.Edge.Agent.Core.Test.csproj index 69005fde9a7..d798744df4c 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/Microsoft.Azure.Devices.Edge.Agent.Core.Test.csproj +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/Microsoft.Azure.Devices.Edge.Agent.Core.Test.csproj @@ -37,4 +37,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleConnectionStringBuilderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleConnectionStringBuilderTest.cs index c0e76538dd3..1b6ffe0ff4b 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleConnectionStringBuilderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleConnectionStringBuilderTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test { using System; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -52,9 +51,9 @@ public void ImplicitOperatorTest() public void InvalidInputsTest() { Assert.Throws(() => new ModuleConnectionStringBuilder(null, "1")); - Assert.Throws(() => new ModuleConnectionStringBuilder("", "1")); + Assert.Throws(() => new ModuleConnectionStringBuilder(string.Empty, "1")); Assert.Throws(() => new ModuleConnectionStringBuilder("iothub", null)); - Assert.Throws(() => new ModuleConnectionStringBuilder("iothub", "")); + Assert.Throws(() => new ModuleConnectionStringBuilder("iothub", string.Empty)); var builder = new ModuleConnectionStringBuilder("foo.azure.com", "device1"); Assert.Throws(() => builder.Create(null)); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleIdentityProviderServiceBuilderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleIdentityProviderServiceBuilderTest.cs index 40d8adcea0a..c59aba3ae32 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleIdentityProviderServiceBuilderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleIdentityProviderServiceBuilderTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test { using System; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -22,7 +21,6 @@ public void TestCreateIdentity_WithEdgelet_DefaultAuthScheme_ShouldCreateIdentit // Act IModuleIdentity identity = builder.Create(moduleId, generationId, edgeletUri); - // Assert Assert.Equal(iotHubHostName, identity.IotHubHostname); Assert.Equal(gatewayHostName, identity.GatewayHostname); @@ -48,7 +46,6 @@ public void TestCreateIdentity_WithEdgelet_SetAuthScheme_ShouldCreateIdentity(st // Act IModuleIdentity identity = builder.Create(moduleId, generationId, edgeletUri, authScheme); - // Assert Assert.Equal(iotHubHostName, identity.IotHubHostname); Assert.Equal(gatewayHostName, identity.GatewayHostname); @@ -66,9 +63,9 @@ public void TestCreateIdentity_WithEdgelet_SetAuthScheme_ShouldCreateIdentity(st public void InvalidInputsTest() { Assert.Throws(() => new ModuleIdentityProviderServiceBuilder(null, "1", "gateway")); - Assert.Throws(() => new ModuleIdentityProviderServiceBuilder("", "1", "gateway")); + Assert.Throws(() => new ModuleIdentityProviderServiceBuilder(string.Empty, "1", "gateway")); Assert.Throws(() => new ModuleIdentityProviderServiceBuilder("iothub", null, "gateway")); - Assert.Throws(() => new ModuleIdentityProviderServiceBuilder("iothub", "", "gateway")); + Assert.Throws(() => new ModuleIdentityProviderServiceBuilder("iothub", string.Empty, "gateway")); var builder = new ModuleIdentityProviderServiceBuilder("foo.azure.com", "device1", "gateway"); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleSetTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleSetTest.cs index a3c374e3a55..323c63e3216 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleSetTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/ModuleSetTest.cs @@ -26,40 +26,6 @@ public class ModuleSetTest static readonly ModuleSet ModuleSet1 = ModuleSet.Create(Module1); static readonly ModuleSet ModuleSet2 = ModuleSet.Create(Module5, Module3); - static class TestApplyDiffSource - { - public static IEnumerable TestData => Data; - - // TODO Add more test cases - static readonly IList Data = new List - { - new object[] - { - ModuleSet.Create(Module1, Module2), - new Diff(new List { Module4, Module3 }, new List { "mod2" }), - ModuleSet.Create(Module3, Module4) - }, - new object[] - { - ModuleSet.Empty, - Diff.Create(Module2, Module1), - ModuleSet.Create(Module1, Module2) - }, - new object[] - { - ModuleSet.Create(Module1, Module2), - new Diff(ImmutableList.Empty, new List { "mod2", "mod1" }), - ModuleSet.Empty - }, - new object[] - { - ModuleSet2, - Diff.Empty, - ModuleSet2 - } - }; - } - [Theory] [Unit] [MemberData(nameof(TestApplyDiffSource.TestData), MemberType = typeof(TestApplyDiffSource))] @@ -75,50 +41,13 @@ public void TestApplyDiff(ModuleSet starting, Diff diff, ModuleSet expected) } } - static class DiffTestSet - { - public static IEnumerable TestData => Data; - - static readonly IList Data = new List - { - // adding modules - new object[] - { - ModuleSet.Empty, - ModuleSet.Create(Module1, Module2), - new Diff(new List { Module1, Module2 }, new List()), - }, - // removing modules - new object[] - { - ModuleSet.Create(Module1, Module2), - ModuleSet.Empty, - new Diff(new List(), new List(){ "mod1", "mod2" }), - }, - // change a module - new object[] - { - ModuleSet.Create(Module2, Module1), - ModuleSet.Create(Module4, Module2), - new Diff(new List() {Module4 }, new List()) - }, - // no changes - new object[] - { - ModuleSet.Create(Module5, Module3, Module2), - ModuleSet.Create(Module2, Module3, Module5), - Diff.Empty - } - }; - } - [Theory] [Unit] [MemberData(nameof(DiffTestSet.TestData), MemberType = typeof(DiffTestSet))] - public void TestDiff(ModuleSet start, ModuleSet end, Diff expected ) + public void TestDiff(ModuleSet start, ModuleSet end, Diff expected) { Diff setDifference = end.Diff(start); - Assert.Equal(setDifference,expected); + Assert.Equal(setDifference, expected); } [Fact] @@ -136,7 +65,7 @@ public void TestDeserialize() string noConfigImageJson = "{\"Modules\":{\"mod1\":{\"Version\":\"version1\",\"Type\":\"test\",\"Status\":\"Running\",\"Settings\":{},\"RestartPolicy\":\"on-unhealthy\",\"Configuration\":{\"id\":\"1\"}},\"mod2\":{\"Version\":\"version1\",\"Type\":\"test\",\"Status\":\"Running\",\"settings\":{},\"RestartPolicy\":\"on-unhealthy\",\"configuration\":{\"id\":\"1\"}}}}"; string notATestType = "{\"Modules\":{\"mod1\":{\"Version\":\"version1\",\"Type\":\"not_a_test\",\"Status\":\"Running\",\"Settings\":{},\"RestartPolicy\":\"on-unhealthy\",\"Configuration\":{\"id\":\"1\"}},\"mod2\":{\"Version\":\"version1\",\"Type\":\"test\",\"Status\":\"Running\",\"settings\":{},\"RestartPolicy\":\"on-unhealthy\",\"configuration\":{\"id\":\"1\"}}}}"; - var serializerInputTable = new Dictionary() { { "Test", typeof(TestModule) }}; + var serializerInputTable = new Dictionary() { { "Test", typeof(TestModule) } }; var myModuleSetSerde = new ModuleSetSerde(serializerInputTable); ModuleSet myModuleSet1 = myModuleSetSerde.Deserialize(validJson); @@ -170,7 +99,7 @@ public void TestDeserialize() [Unit] public void ModuleSetSerialize() { - var serializerInputTable = new Dictionary() {{"test", typeof(TestModule)}}; + var serializerInputTable = new Dictionary() { { "test", typeof(TestModule) } }; var myModuleSetSerde = new ModuleSetSerde(serializerInputTable); string jsonFromTestModuleSet = myModuleSetSerde.Serialize(ModuleSet1); @@ -192,5 +121,75 @@ public void ModuleSetSerialize() Assert.True(module5.Equals(module6)); } + static class DiffTestSet + { + static readonly IList Data = new List + { + // adding modules + new object[] + { + ModuleSet.Empty, + ModuleSet.Create(Module1, Module2), + new Diff(new List { Module1, Module2 }, new List()), + }, + // removing modules + new object[] + { + ModuleSet.Create(Module1, Module2), + ModuleSet.Empty, + new Diff(new List(), new List() { "mod1", "mod2" }), + }, + // change a module + new object[] + { + ModuleSet.Create(Module2, Module1), + ModuleSet.Create(Module4, Module2), + new Diff(new List() { Module4 }, new List()) + }, + // no changes + new object[] + { + ModuleSet.Create(Module5, Module3, Module2), + ModuleSet.Create(Module2, Module3, Module5), + Diff.Empty + } + }; + + public static IEnumerable TestData => Data; + } + + static class TestApplyDiffSource + { + // TODO Add more test cases + static readonly IList Data = new List + { + new object[] + { + ModuleSet.Create(Module1, Module2), + new Diff(new List { Module4, Module3 }, new List { "mod2" }), + ModuleSet.Create(Module3, Module4) + }, + new object[] + { + ModuleSet.Empty, + Diff.Create(Module2, Module1), + ModuleSet.Create(Module1, Module2) + }, + new object[] + { + ModuleSet.Create(Module1, Module2), + new Diff(ImmutableList.Empty, new List { "mod2", "mod1" }), + ModuleSet.Empty + }, + new object[] + { + ModuleSet2, + Diff.Empty, + ModuleSet2 + } + }; + + public static IEnumerable TestData => Data; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/NullEnvironmentTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/NullEnvironmentTest.cs index 96d855cb987..606dab19bed 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/NullEnvironmentTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/NullEnvironmentTest.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test { using System.Threading; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; using Xunit; public class NullEnvironmentTest diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedPlanRunnerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedPlanRunnerTest.cs index 7b86ade2433..a719a4fec35 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedPlanRunnerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedPlanRunnerTest.cs @@ -29,7 +29,6 @@ public async void TestPlanExecution() new TestRecordType(TestCommandType.TestCreate, new TestModule("module3", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image3"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars)), new TestRecordType(TestCommandType.TestCreate, new TestModule("module4", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image4"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars)), new TestRecordType(TestCommandType.TestCreate, new TestModule("module5", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image5"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars)), - }; var commandList = new List { @@ -51,7 +50,8 @@ public async void TestPlanExecution() var planRunner = new OrderedPlanRunner(); await planRunner.ExecuteAsync(1, plan1, token); - Assert.All(commandList, + Assert.All( + commandList, command => { var c = command as TestCommand; @@ -88,7 +88,8 @@ await factory.StopAsync(moduleExecutionList[4].Module), var token = new CancellationToken(); var planRunner = new OrderedPlanRunner(); await planRunner.ExecuteAsync(1, plan1, token); - Assert.All(commandList, + Assert.All( + commandList, command => { var c = command as TestCommand; @@ -133,36 +134,26 @@ await factory.StopAsync(moduleExecutionList[4].Module), AggregateException ex = await Assert.ThrowsAsync(async () => await planRunner.ExecuteAsync(1, plan1, token)); Assert.True(ex.InnerExceptions.Count == commandList.Count / 2); - Assert.True(commandList.Where(command => - { - var c = command as TestCommand; - Assert.NotNull(c); - return c.CommandExecuted; - }).Count() == commandList.Count / 2); - Assert.True(commandList.Where(command => - { - var c = command as TestCommand; - Assert.NotNull(c); - return !c.CommandExecuted; - }).Count() == commandList.Count / 2); + Assert.True( + commandList.Where( + command => + { + var c = command as TestCommand; + Assert.NotNull(c); + return c.CommandExecuted; + }).Count() == commandList.Count / 2); + Assert.True( + commandList.Where( + command => + { + var c = command as TestCommand; + Assert.NotNull(c); + return !c.CommandExecuted; + }).Count() == commandList.Count / 2); factory.Recorder.ForEach(r => Assert.Equal(moduleExecutionList, r.ExecutionList)); } - Mock MakeMockCommand(string id, Action callback = null) - { - callback = callback ?? (() => { }); - - var command = new Mock(); - command.SetupGet(c => c.Id).Returns(id); - command.Setup(c => c.ExecuteAsync(It.IsAny())) - .Callback(callback) - .Returns(Task.CompletedTask); - command.Setup(c => c.Show()) - .Returns(id); - return command; - } - [Fact] [Unit] public async void TestOrderedPlanRunnerCancellation() @@ -186,5 +177,19 @@ public async void TestOrderedPlanRunnerCancellation() commands[1].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); commands[2].Verify(m => m.ExecuteAsync(cts.Token), Times.Never()); } + + Mock MakeMockCommand(string id, Action callback = null) + { + callback = callback ?? (() => { }); + + var command = new Mock(); + command.SetupGet(c => c.Id).Returns(id); + command.Setup(c => c.ExecuteAsync(It.IsAny())) + .Callback(callback) + .Returns(Task.CompletedTask); + command.Setup(c => c.Show()) + .Returns(id); + return command; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedRetryPlanRunnerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedRetryPlanRunnerTest.cs index 5996ca304a6..d45e7dbb018 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedRetryPlanRunnerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/OrderedRetryPlanRunnerTest.cs @@ -37,12 +37,10 @@ public async void TestExecuteAsyncInputs() // Act // Assert await Assert.ThrowsAsync( - () => runner.ExecuteAsync(-2, plan, token) - ); + () => runner.ExecuteAsync(-2, plan, token)); await Assert.ThrowsAsync( - () => runner.ExecuteAsync(10, null, token) - ); + () => runner.ExecuteAsync(10, null, token)); } [Fact] @@ -310,31 +308,6 @@ public async void ExecuteAsyncResetsStatsOnFailingCommandOnceItSucceeds() badCommand.Verify(c => c.ExecuteAsync(token), Times.Exactly(4)); } - Mock MakeMockCommandThatWorks(string id, Action callback = null) - { - callback = callback ?? (() => { }); - - var command = new Mock(); - command.SetupGet(c => c.Id).Returns(id); - command.Setup(c => c.ExecuteAsync(It.IsAny())) - .Callback(callback) - .Returns(Task.CompletedTask); - command.Setup(c => c.Show()) - .Returns(id); - return command; - } - - Mock MakeMockCommandThatThrows(string id) - { - var command = new Mock(); - command.SetupGet(c => c.Id).Returns(id); - command.Setup(c => c.ExecuteAsync(It.IsAny())) - .ThrowsAsync(new InvalidOperationException("No donuts for you")); - command.Setup(c => c.Show()) - .Returns(id); - return command; - } - [Fact] [Unit] public async void TestOrderedRetryPlanRunnerCancellation() @@ -361,5 +334,30 @@ public async void TestOrderedRetryPlanRunnerCancellation() commands[1].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); commands[2].Verify(m => m.ExecuteAsync(cts.Token), Times.Never()); } + + Mock MakeMockCommandThatWorks(string id, Action callback = null) + { + callback = callback ?? (() => { }); + + var command = new Mock(); + command.SetupGet(c => c.Id).Returns(id); + command.Setup(c => c.ExecuteAsync(It.IsAny())) + .Callback(callback) + .Returns(Task.CompletedTask); + command.Setup(c => c.Show()) + .Returns(id); + return command; + } + + Mock MakeMockCommandThatThrows(string id) + { + var command = new Mock(); + command.SetupGet(c => c.Id).Returns(id); + command.Setup(c => c.ExecuteAsync(It.IsAny())) + .ThrowsAsync(new InvalidOperationException("No donuts for you")); + command.Setup(c => c.Show()) + .Returns(id); + return command; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/RestartPolicyManagerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/RestartPolicyManagerTest.cs index 1f1a6602cfc..d373cff1b6b 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/RestartPolicyManagerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/RestartPolicyManagerTest.cs @@ -12,7 +12,7 @@ public class RestartPolicyManagerTest { const int MaxRestartCount = 5; const int CoolOffTimeUnitInSeconds = 10; - + [Fact] [Unit] public void TestCreateValidation() @@ -24,174 +24,6 @@ public void TestCreateValidation() Assert.NotNull(new RestartPolicyManager(5, 10)); } - static IEnumerable GetTestDataForComputeModuleStatusFromRestartPolicy() - { - ( - ModuleStatus status, - RestartPolicy restartPolicy, - int restartCount, - ModuleStatus expectedStatus - )[] data = - { - // Running - ( - ModuleStatus.Running, - RestartPolicy.Never, - 0, - ModuleStatus.Running - ), - ( - ModuleStatus.Running, - RestartPolicy.OnFailure, - 0, - ModuleStatus.Running - ), - ( - ModuleStatus.Running, - RestartPolicy.OnUnhealthy, - 0, - ModuleStatus.Running - ), - ( - ModuleStatus.Running, - RestartPolicy.Always, - 0, - ModuleStatus.Running - ), - - // Unhealthy - ( - ModuleStatus.Unhealthy, - RestartPolicy.Never, - 0, - ModuleStatus.Unhealthy - ), - ( - ModuleStatus.Unhealthy, - RestartPolicy.OnFailure, - 0, - ModuleStatus.Backoff - ), - ( - ModuleStatus.Unhealthy, - RestartPolicy.OnUnhealthy, - 0, - ModuleStatus.Backoff - ), - ( - ModuleStatus.Unhealthy, - RestartPolicy.Always, - 0, - ModuleStatus.Backoff - ), - - // Unhealthy / RestartCount > MaxRestartCount - ( - ModuleStatus.Unhealthy, - RestartPolicy.OnFailure, - MaxRestartCount + 1, - ModuleStatus.Failed - ), - ( - ModuleStatus.Unhealthy, - RestartPolicy.OnUnhealthy, - MaxRestartCount + 1, - ModuleStatus.Failed - ), - ( - ModuleStatus.Unhealthy, - RestartPolicy.Always, - MaxRestartCount + 1, - ModuleStatus.Failed - ), - - // Stopped - ( - ModuleStatus.Stopped, - RestartPolicy.Never, - 0, - ModuleStatus.Stopped - ), - ( - ModuleStatus.Stopped, - RestartPolicy.OnFailure, - 0, - ModuleStatus.Stopped - ), - ( - ModuleStatus.Stopped, - RestartPolicy.OnUnhealthy, - 0, - ModuleStatus.Stopped - ), - ( - ModuleStatus.Stopped, - RestartPolicy.Always, - 0, - ModuleStatus.Backoff - ), - - // Stopped / RestartCount > MaxRestartCount - ( - ModuleStatus.Stopped, - RestartPolicy.Always, - MaxRestartCount + 1, - ModuleStatus.Failed - ), - - // Failed - ( - ModuleStatus.Failed, - RestartPolicy.Never, - 0, - ModuleStatus.Failed - ), - ( - ModuleStatus.Failed, - RestartPolicy.OnFailure, - 0, - ModuleStatus.Backoff - ), - ( - ModuleStatus.Failed, - RestartPolicy.OnUnhealthy, - 0, - ModuleStatus.Backoff - ), - ( - ModuleStatus.Failed, - RestartPolicy.Always, - 0, - ModuleStatus.Backoff - ), - - // Failed / RestartCount > MaxRestartCount - ( - ModuleStatus.Failed, - RestartPolicy.OnFailure, - MaxRestartCount + 1, - ModuleStatus.Failed - ), - ( - ModuleStatus.Failed, - RestartPolicy.OnUnhealthy, - MaxRestartCount + 1, - ModuleStatus.Failed - ), - ( - ModuleStatus.Failed, - RestartPolicy.Always, - MaxRestartCount + 1, - ModuleStatus.Failed - ) - }; - - return data.Select(d => new object[] - { - d.status, d.restartPolicy, d.restartCount, d.expectedStatus - }); - } - [Fact] [Unit] public void TestComputeModuleStatusFromRestartPolicyInvalidStatus() @@ -209,431 +41,6 @@ public void TestComputeModuleStatusFromRestartPolicy(ModuleStatus status, Restar Assert.Equal(expectedStatus, manager.ComputeModuleStatusFromRestartPolicy(status, restartPolicy, restartCount, DateTime.MinValue)); } - static IEnumerable GetTestDataForApplyRestartPolicy() - { - ( - RestartPolicy restartPolicy, - ModuleStatus runtimeStatus, - int restartCount, - Func getLastExitTimeUtc, - bool apply - )[] data = - { - ////////////////////////////////// - // RestartPolicy - Always - ////////////////////////////////// - - // Running - ( - RestartPolicy.Always, - ModuleStatus.Running, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Always, - ModuleStatus.Running, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Failed - ( - RestartPolicy.Always, - ModuleStatus.Failed, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Always, - ModuleStatus.Failed, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Unhealthy - ( - RestartPolicy.Always, - ModuleStatus.Unhealthy, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Always, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.Always, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Backoff - ( - RestartPolicy.Always, - ModuleStatus.Backoff, - 0, - () => DateTime.MinValue, - true - ), - ( - RestartPolicy.Always, - ModuleStatus.Backoff, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - true - ), - ( - RestartPolicy.Always, - ModuleStatus.Backoff, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Stopped - ( - RestartPolicy.Always, - ModuleStatus.Stopped, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Always, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.Always, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - ////////////////////////////////// - // RestartPolicy - OnUnhealthy - ////////////////////////////////// - - // Running - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Running, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Running, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Failed - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Failed, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Failed, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Unhealthy - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Unhealthy, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Backoff - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Backoff, - 0, - () => DateTime.MinValue, - true - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Backoff, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - true - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Backoff, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Stopped - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Stopped, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.OnUnhealthy, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - ////////////////////////////////// - // RestartPolicy - OnFailure - ////////////////////////////////// - - // Running - ( - RestartPolicy.OnFailure, - ModuleStatus.Running, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Running, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Failed - ( - RestartPolicy.OnFailure, - ModuleStatus.Failed, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Failed, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Unhealthy - ( - RestartPolicy.OnFailure, - ModuleStatus.Unhealthy, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Backoff - ( - RestartPolicy.OnFailure, - ModuleStatus.Backoff, - 0, - () => DateTime.MinValue, - true - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Backoff, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - true - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Backoff, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Stopped - ( - RestartPolicy.OnFailure, - ModuleStatus.Stopped, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.OnFailure, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - ////////////////////////////////// - // RestartPolicy - Never - ////////////////////////////////// - - // Running - ( - RestartPolicy.Never, - ModuleStatus.Running, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Never, - ModuleStatus.Running, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Failed - ( - RestartPolicy.Never, - ModuleStatus.Failed, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Never, - ModuleStatus.Failed, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - - // Unhealthy - ( - RestartPolicy.Never, - ModuleStatus.Unhealthy, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Never, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.Never, - ModuleStatus.Unhealthy, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ), - - // Stopped - ( - RestartPolicy.Never, - ModuleStatus.Stopped, - 0, - () => DateTime.MinValue, - false - ), - ( - RestartPolicy.Never, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromHours(1), - false - ), - ( - RestartPolicy.Never, - ModuleStatus.Stopped, - 3, - () => DateTime.UtcNow - TimeSpan.FromSeconds(40), - false - ) - }; - - return data.Select(d => new object[] - { - d.restartPolicy, d.runtimeStatus, d.restartCount, d.getLastExitTimeUtc, d.apply - }); - } - - static Mock CreateMockRuntimeModule(RestartPolicy restartPolicy, ModuleStatus runtimeStatus, int restartCount, DateTime lastExitTimeUtc) - { - var module = new Mock(); - module.Setup(m => m.RestartPolicy).Returns(restartPolicy); - module.Setup(m => m.RuntimeStatus).Returns(runtimeStatus); - module.Setup(m => m.RestartCount).Returns(restartCount); - module.Setup(m => m.LastExitTimeUtc).Returns(lastExitTimeUtc); - - return module; - } - [Theory] [MemberData(nameof(GetTestDataForApplyRestartPolicy))] [Unit] @@ -642,8 +49,7 @@ public void TestApplyRestartPolicy( ModuleStatus runtimeStatus, int restartCount, Func getLastExitTimeUtc, - bool apply - ) + bool apply) { // Arrange var manager = new RestartPolicyManager(MaxRestartCount, CoolOffTimeUnitInSeconds); @@ -706,5 +112,600 @@ public void CoolOffPeriodTest(int restartCount, int expectedCoolOffPeriodSecs) // Assert Assert.Equal(expectedCoolOffPeriodSecs, coolOffPeriod.TotalSeconds); } + + static IEnumerable GetTestDataForComputeModuleStatusFromRestartPolicy() + { + ( + ModuleStatus status, + RestartPolicy restartPolicy, + int restartCount, + ModuleStatus expectedStatus + )[] data = + { + // Running + ( + ModuleStatus.Running, + RestartPolicy.Never, + 0, + ModuleStatus.Running + ), + ( + ModuleStatus.Running, + RestartPolicy.OnFailure, + 0, + ModuleStatus.Running + ), + ( + ModuleStatus.Running, + RestartPolicy.OnUnhealthy, + 0, + ModuleStatus.Running + ), + ( + ModuleStatus.Running, + RestartPolicy.Always, + 0, + ModuleStatus.Running + ), + + // Unhealthy + ( + ModuleStatus.Unhealthy, + RestartPolicy.Never, + 0, + ModuleStatus.Unhealthy + ), + ( + ModuleStatus.Unhealthy, + RestartPolicy.OnFailure, + 0, + ModuleStatus.Backoff + ), + ( + ModuleStatus.Unhealthy, + RestartPolicy.OnUnhealthy, + 0, + ModuleStatus.Backoff + ), + ( + ModuleStatus.Unhealthy, + RestartPolicy.Always, + 0, + ModuleStatus.Backoff + ), + + // Unhealthy / RestartCount > MaxRestartCount + ( + ModuleStatus.Unhealthy, + RestartPolicy.OnFailure, + MaxRestartCount + 1, + ModuleStatus.Failed + ), + ( + ModuleStatus.Unhealthy, + RestartPolicy.OnUnhealthy, + MaxRestartCount + 1, + ModuleStatus.Failed + ), + ( + ModuleStatus.Unhealthy, + RestartPolicy.Always, + MaxRestartCount + 1, + ModuleStatus.Failed + ), + + // Stopped + ( + ModuleStatus.Stopped, + RestartPolicy.Never, + 0, + ModuleStatus.Stopped + ), + ( + ModuleStatus.Stopped, + RestartPolicy.OnFailure, + 0, + ModuleStatus.Stopped + ), + ( + ModuleStatus.Stopped, + RestartPolicy.OnUnhealthy, + 0, + ModuleStatus.Stopped + ), + ( + ModuleStatus.Stopped, + RestartPolicy.Always, + 0, + ModuleStatus.Backoff + ), + + // Stopped / RestartCount > MaxRestartCount + ( + ModuleStatus.Stopped, + RestartPolicy.Always, + MaxRestartCount + 1, + ModuleStatus.Failed + ), + + // Failed + ( + ModuleStatus.Failed, + RestartPolicy.Never, + 0, + ModuleStatus.Failed + ), + ( + ModuleStatus.Failed, + RestartPolicy.OnFailure, + 0, + ModuleStatus.Backoff + ), + ( + ModuleStatus.Failed, + RestartPolicy.OnUnhealthy, + 0, + ModuleStatus.Backoff + ), + ( + ModuleStatus.Failed, + RestartPolicy.Always, + 0, + ModuleStatus.Backoff + ), + + // Failed / RestartCount > MaxRestartCount + ( + ModuleStatus.Failed, + RestartPolicy.OnFailure, + MaxRestartCount + 1, + ModuleStatus.Failed + ), + ( + ModuleStatus.Failed, + RestartPolicy.OnUnhealthy, + MaxRestartCount + 1, + ModuleStatus.Failed + ), + ( + ModuleStatus.Failed, + RestartPolicy.Always, + MaxRestartCount + 1, + ModuleStatus.Failed + ) + }; + + return data.Select( + d => new object[] + { + d.status, d.restartPolicy, d.restartCount, d.expectedStatus + }); + } + + static IEnumerable GetTestDataForApplyRestartPolicy() + { + ( + RestartPolicy restartPolicy, + ModuleStatus runtimeStatus, + int restartCount, + Func getLastExitTimeUtc, + bool apply + )[] data = + { + ////////////////////////////////// + // RestartPolicy - Always + ////////////////////////////////// + + // Running + ( + RestartPolicy.Always, + ModuleStatus.Running, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Always, + ModuleStatus.Running, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Failed + ( + RestartPolicy.Always, + ModuleStatus.Failed, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Always, + ModuleStatus.Failed, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Unhealthy + ( + RestartPolicy.Always, + ModuleStatus.Unhealthy, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Always, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.Always, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Backoff + ( + RestartPolicy.Always, + ModuleStatus.Backoff, + 0, + () => DateTime.MinValue, + true + ), + ( + RestartPolicy.Always, + ModuleStatus.Backoff, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + true + ), + ( + RestartPolicy.Always, + ModuleStatus.Backoff, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Stopped + ( + RestartPolicy.Always, + ModuleStatus.Stopped, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Always, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.Always, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + ////////////////////////////////// + // RestartPolicy - OnUnhealthy + ////////////////////////////////// + + // Running + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Running, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Running, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Failed + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Failed, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Failed, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Unhealthy + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Unhealthy, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Backoff + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Backoff, + 0, + () => DateTime.MinValue, + true + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Backoff, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + true + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Backoff, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Stopped + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Stopped, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.OnUnhealthy, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + ////////////////////////////////// + // RestartPolicy - OnFailure + ////////////////////////////////// + + // Running + ( + RestartPolicy.OnFailure, + ModuleStatus.Running, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Running, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Failed + ( + RestartPolicy.OnFailure, + ModuleStatus.Failed, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Failed, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Unhealthy + ( + RestartPolicy.OnFailure, + ModuleStatus.Unhealthy, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Backoff + ( + RestartPolicy.OnFailure, + ModuleStatus.Backoff, + 0, + () => DateTime.MinValue, + true + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Backoff, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + true + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Backoff, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Stopped + ( + RestartPolicy.OnFailure, + ModuleStatus.Stopped, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.OnFailure, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + ////////////////////////////////// + // RestartPolicy - Never + ////////////////////////////////// + + // Running + ( + RestartPolicy.Never, + ModuleStatus.Running, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Never, + ModuleStatus.Running, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Failed + ( + RestartPolicy.Never, + ModuleStatus.Failed, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Never, + ModuleStatus.Failed, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + + // Unhealthy + ( + RestartPolicy.Never, + ModuleStatus.Unhealthy, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Never, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.Never, + ModuleStatus.Unhealthy, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ), + + // Stopped + ( + RestartPolicy.Never, + ModuleStatus.Stopped, + 0, + () => DateTime.MinValue, + false + ), + ( + RestartPolicy.Never, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromHours(1), + false + ), + ( + RestartPolicy.Never, + ModuleStatus.Stopped, + 3, + () => DateTime.UtcNow - TimeSpan.FromSeconds(40), + false + ) + }; + + return data.Select( + d => new object[] + { + d.restartPolicy, d.runtimeStatus, d.restartCount, d.getLastExitTimeUtc, d.apply + }); + } + + static Mock CreateMockRuntimeModule(RestartPolicy restartPolicy, ModuleStatus runtimeStatus, int restartCount, DateTime lastExitTimeUtc) + { + var module = new Mock(); + module.Setup(m => m.RestartPolicy).Returns(restartPolicy); + module.Setup(m => m.RuntimeStatus).Returns(runtimeStatus); + module.Setup(m => m.RestartCount).Returns(restartCount); + module.Setup(m => m.LastExitTimeUtc).Returns(lastExitTimeUtc); + + return module; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestCommand.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestCommand.cs index 4690b0497e5..a80bc21106e 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestCommand.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestCommand.cs @@ -24,7 +24,10 @@ public TestRecordType(TestCommandType testType, IModule module) public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) + { return false; + } + return obj is TestRecordType && this.Equals((TestRecordType)obj); } @@ -39,10 +42,6 @@ public override int GetHashCode() public class TestPlanRecorder { - public List ExecutionList { get; } - public List UndoList { get; } - public List<(TestCommandType Type, ICommand Command)> WrappedCommmandList { get; } - public TestPlanRecorder() { this.ExecutionList = new List(); @@ -50,6 +49,12 @@ public TestPlanRecorder() this.WrappedCommmandList = new List<(TestCommandType Type, ICommand Command)>(); } + public List ExecutionList { get; } + + public List UndoList { get; } + + public List<(TestCommandType Type, ICommand Command)> WrappedCommmandList { get; } + public void ModuleExecuted(TestCommandType type, IModule module) => this.ExecutionList.Add(new TestRecordType(type, module)); public void ModuleUndone(TestCommandType type, IModule module) => this.UndoList.Add(new TestRecordType(type, module)); @@ -59,13 +64,13 @@ public TestPlanRecorder() public class TestCommandFactory : ICommandFactory { - public Option Recorder { get; } - public TestCommandFactory() { this.Recorder = Option.Some(new TestPlanRecorder()); } + public Option Recorder { get; } + public Task CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) { Assert.True(module.Module is TestModule); @@ -118,13 +123,13 @@ public Task WrapAsync(ICommand command) public class TestCommandFailureFactory : ICommandFactory { - public Option Recorder { get; } - public TestCommandFailureFactory() { this.Recorder = Option.Some(new TestPlanRecorder()); } + public Option Recorder { get; } + public Task CreateAsync(IModuleWithIdentity module, IRuntimeInfo runtimeInfo) { Assert.True(module.Module is TestModule); @@ -171,27 +176,30 @@ public Task RestartAsync(IModule module) public Task WrapAsync(ICommand command) { foreach (TestPlanRecorder r in this.Recorder) + { r.CommandWrapped(command); + } return Task.FromResult(command); } } + public class TestCommand : ICommand { + public bool CommandExecuted; + public bool CommandUndone; readonly Option recorder; readonly TestCommandType type; readonly IModule module; readonly bool throwOnExecute; - public bool CommandExecuted; - public bool CommandUndone; - public TestCommand(TestCommandType type, IModule module) : - this(type, module, Option.None(), false) + public TestCommand(TestCommandType type, IModule module) + : this(type, module, Option.None(), false) { } - public TestCommand(TestCommandType type, IModule module, Option recorder) : - this(type, module, recorder, false) + public TestCommand(TestCommandType type, IModule module, Option recorder) + : this(type, module, recorder, false) { } @@ -217,7 +225,10 @@ public Task ExecuteAsync(CancellationToken token) } foreach (TestPlanRecorder r in this.recorder) + { r.ModuleExecuted(this.type, this.module); + } + this.CommandExecuted = true; return TaskEx.Done; } @@ -225,7 +236,10 @@ public Task ExecuteAsync(CancellationToken token) public Task UndoAsync(CancellationToken token) { foreach (TestPlanRecorder r in this.recorder) + { r.ModuleUndone(this.type, this.module); + } + this.CommandUndone = true; return TaskEx.Done; } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModule.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModule.cs index e05b1330ef8..293d08c2d72 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModule.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModule.cs @@ -4,33 +4,41 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test using System; using System.Collections.Generic; using System.Collections.Immutable; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util; using Newtonsoft.Json; public class TestConfig : IEquatable { - [JsonProperty(Required = Required.Always, PropertyName = "image")] - public string Image { get; } - public TestConfig(string image) { this.Image = Preconditions.CheckNotNull(image, nameof(image)); } + [JsonProperty(Required = Required.Always, PropertyName = "image")] + public string Image { get; } + public bool Equals(TestConfig other) { if (ReferenceEquals(null, other)) + { return false; + } + return ReferenceEquals(this, other) || string.Equals(this.Image, other.Image); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) + { return false; + } + if (ReferenceEquals(this, obj)) + { return true; + } + return obj.GetType() == this.GetType() && this.Equals((TestConfig)obj); } @@ -39,6 +47,27 @@ public override bool Equals(object obj) public class TestModuleBase : IModule { + [JsonConstructor] + public TestModuleBase( + string name, + string version, + string type, + ModuleStatus desiredStatus, + TConfig config, + RestartPolicy restartPolicy, + ConfigurationInfo configuration, + IDictionary env) + { + this.Name = name; + this.Version = Preconditions.CheckNotNull(version, nameof(version)); + this.Type = Preconditions.CheckNotNull(type, nameof(type)); + this.DesiredStatus = Preconditions.CheckNotNull(desiredStatus, nameof(desiredStatus)); + this.Config = Preconditions.CheckNotNull(config, nameof(config)); + this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); + this.ConfigurationInfo = configuration ?? new ConfigurationInfo(); + this.Env = env?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; + } + [JsonIgnore] public string Name { get; set; } @@ -63,20 +92,6 @@ public class TestModuleBase : IModule [JsonProperty("env")] public IDictionary Env { get; } - [JsonConstructor] - public TestModuleBase(string name, string version, string type, ModuleStatus desiredStatus, - TConfig config, RestartPolicy restartPolicy, ConfigurationInfo configuration, IDictionary env) - { - this.Name = name; - this.Version = Preconditions.CheckNotNull(version, nameof(version)); - this.Type = Preconditions.CheckNotNull(type, nameof(type)); - this.DesiredStatus = Preconditions.CheckNotNull(desiredStatus, nameof(desiredStatus)); - this.Config = Preconditions.CheckNotNull(config, nameof(config)); - this.RestartPolicy = Preconditions.CheckIsDefined(restartPolicy); - this.ConfigurationInfo = configuration ?? new ConfigurationInfo(); - this.Env = env?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; - } - public override bool Equals(object obj) => this.Equals(obj as TestModuleBase); public bool Equals(IModule other) => this.Equals(other as TestModuleBase); @@ -84,15 +99,21 @@ public TestModuleBase(string name, string version, string type, ModuleStatus des public virtual bool Equals(IModule other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return string.Equals(this.Name, other.Name) && - string.Equals(this.Version, other.Version) && - string.Equals(this.Type, other.Type) && - this.DesiredStatus == other.DesiredStatus && - this.Config.Equals(other.Config) && - this.RestartPolicy == other.RestartPolicy; + string.Equals(this.Version, other.Version) && + string.Equals(this.Type, other.Type) && + this.DesiredStatus == other.DesiredStatus && + this.Config.Equals(other.Config) && + this.RestartPolicy == other.RestartPolicy; } public override int GetHashCode() @@ -102,7 +123,7 @@ public override int GetHashCode() // We are ignoring this here because, we only change the name of the module on Creation. This // is needed because the name is not part of the body of Json equivalent to IModule, it is on the key of the json. // ReSharper disable NonReadonlyMemberInGetHashCode - int hashCode = (this.Name != null ? this.Name.GetHashCode() : 0); + int hashCode = this.Name != null ? this.Name.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (this.Version != null ? this.Version.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.Type != null ? this.Type.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (int)this.DesiredStatus; @@ -115,8 +136,14 @@ public override int GetHashCode() public class TestModule : TestModuleBase { - public TestModule(string name, string version, string type, ModuleStatus desiredStatus, - TestConfig config, RestartPolicy restartPolicy, ConfigurationInfo configuration, + public TestModule( + string name, + string version, + string type, + ModuleStatus desiredStatus, + TestConfig config, + RestartPolicy restartPolicy, + ConfigurationInfo configuration, IDictionary env) : base(name, version, type, desiredStatus, config, restartPolicy, configuration, env) { @@ -130,6 +157,14 @@ public TestModule CloneWithImage(string image) public class TestAgentModule : TestModule, IEdgeAgentModule { + public TestAgentModule(string name, string type, TestConfig config, ConfigurationInfo configuration, IDictionary env) + : base(name ?? Constants.EdgeAgentModuleName, string.Empty, type, ModuleStatus.Running, config, RestartPolicy.Always, configuration, env) + { + this.Version = string.Empty; + this.RestartPolicy = RestartPolicy.Always; + this.DesiredStatus = ModuleStatus.Running; + } + [JsonIgnore] public override string Version { get; } @@ -139,30 +174,21 @@ public class TestAgentModule : TestModule, IEdgeAgentModule [JsonIgnore] public override ModuleStatus DesiredStatus { get; } - public TestAgentModule(string name, string type, TestConfig config, ConfigurationInfo configuration, IDictionary env) - : base(name ?? Constants.EdgeAgentModuleName, string.Empty, type, ModuleStatus.Running, config, RestartPolicy.Always, configuration, env) - { - this.Version = string.Empty; - this.RestartPolicy = RestartPolicy.Always; - this.DesiredStatus = ModuleStatus.Running; - } - public virtual IModule WithRuntimeStatus(ModuleStatus newStatus) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } public class TestHubModule : TestModule, IEdgeHubModule { - [JsonIgnore] - public override string Version { get; } - public TestHubModule(string name, string type, ModuleStatus desiredStatus, TestConfig config, RestartPolicy restartPolicy, ConfigurationInfo configuration, IDictionary env) : base(name ?? Constants.EdgeHubModuleName, string.Empty, type, desiredStatus, config, restartPolicy, configuration, env) { this.Version = string.Empty; } - } + [JsonIgnore] + public override string Version { get; } + } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModuleTest.cs index 27fe455834d..1c938ce49f0 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestModuleTest.cs @@ -2,14 +2,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test { using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Linq; using Microsoft.Azure.Devices.Edge.Agent.Core.Serde; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Newtonsoft.Json; - using Xunit; using Newtonsoft.Json.Linq; - using System.Linq; - using System.Collections.Generic; + using Xunit; [ExcludeFromCodeCoverage] public class TestModuleTest @@ -33,7 +33,8 @@ public class TestModuleTest static readonly IModule ValidJsonModule = new TestModule("", "", "docker", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); static readonly string serializedModule = "{\"version\":\"version1\",\"type\":\"type1\",\"status\":\"running\",\"settings\":{\"image\":\"image1\"},\"restartPolicy\":\"on-unhealthy\",\"configuration\":{\"id\":\"1\"}}"; - static readonly JObject TestJsonInputs = JsonConvert.DeserializeObject(@" + static readonly JObject TestJsonInputs = JsonConvert.DeserializeObject( + @" { ""validJson"": [ { @@ -221,22 +222,6 @@ public void TestEquality() Assert.False(Config1.Equals(null)); } - static IEnumerable GetJsonTestCases(string subset) - { - var val = (JArray)TestJsonInputs.GetValue(subset); - return val.Children().Select(token => token.ToString()); - } - - static IEnumerable GetValidJsonInputs() - { - return GetJsonTestCases("validJson").Select(s => new object[] { s }); - } - - static IEnumerable GetExceptionJsonInputs() - { - return GetJsonTestCases("throwsException").Select(s => new object[] { s }); - } - [Theory] [Unit] [MemberData(nameof(GetValidJsonInputs))] @@ -288,5 +273,21 @@ public void TestSerialize() Assert.True(moduleFromSerializedModule.Equals(Module8)); Assert.True(moduleFromSerializedModule.Equals(Module1)); } + + static IEnumerable GetJsonTestCases(string subset) + { + var val = (JArray)TestJsonInputs.GetValue(subset); + return val.Children().Select(token => token.ToString()); + } + + static IEnumerable GetValidJsonInputs() + { + return GetJsonTestCases("validJson").Select(s => new object[] { s }); + } + + static IEnumerable GetExceptionJsonInputs() + { + return GetJsonTestCases("throwsException").Select(s => new object[] { s }); + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModule.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModule.cs index f1fabb4057a..a16abc49499 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModule.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModule.cs @@ -3,11 +3,37 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test { using System; using System.Collections.Generic; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Newtonsoft.Json; public class TestRuntimeModule : TestModule, IRuntimeModule { + public TestRuntimeModule( + string name, + string version, + RestartPolicy restartPolicy, + string type, + ModuleStatus desiredStatus, + TestConfig config, + int exitCode, + string statusDescription, + DateTime lastStartTimeUtc, + DateTime lastExitTimeUtc, + int restartCount, + DateTime lastRestartTimeUtc, + ModuleStatus runtimeStatus, + ConfigurationInfo deploymentInfo = null, + IDictionary env = null) + : base(name, version, type, desiredStatus, config, restartPolicy, deploymentInfo, env) + { + this.ExitCode = exitCode; + this.StatusDescription = statusDescription; + this.LastStartTimeUtc = lastStartTimeUtc; + this.LastExitTimeUtc = lastExitTimeUtc; + this.RestartCount = restartCount; + this.LastRestartTimeUtc = lastRestartTimeUtc; + this.RuntimeStatus = runtimeStatus; + } + [JsonProperty(PropertyName = "exitCode")] public int ExitCode { get; } @@ -29,22 +55,6 @@ public class TestRuntimeModule : TestModule, IRuntimeModule [JsonProperty(PropertyName = "runtimeStatus")] public ModuleStatus RuntimeStatus { get; } - public TestRuntimeModule( - string name, string version, RestartPolicy restartPolicy, string type, ModuleStatus desiredStatus, - TestConfig config, int exitCode, string statusDescription, DateTime lastStartTimeUtc, - DateTime lastExitTimeUtc, int restartCount, DateTime lastRestartTimeUtc, ModuleStatus runtimeStatus, - ConfigurationInfo deploymentInfo = null, IDictionary env = null) - : base(name, version, type, desiredStatus, config, restartPolicy, deploymentInfo, env) - { - this.ExitCode = exitCode; - this.StatusDescription = statusDescription; - this.LastStartTimeUtc = lastStartTimeUtc; - this.LastExitTimeUtc = lastExitTimeUtc; - this.RestartCount = restartCount; - this.LastRestartTimeUtc = lastRestartTimeUtc; - this.RuntimeStatus = runtimeStatus; - } - public override bool Equals(object obj) => this.Equals(obj as TestModuleBase); public bool Equals(IRuntimeModule other) => this.Equals(other as TestModuleBase); @@ -60,19 +70,18 @@ public override bool Equals(IModule other) { var reported = other as IRuntimeModule; return base.Equals(other) && - this.ExitCode == reported.ExitCode && - string.Equals(this.StatusDescription, reported.StatusDescription) && - this.LastStartTimeUtc == reported.LastStartTimeUtc && - this.LastExitTimeUtc == reported.LastExitTimeUtc && - this.RestartCount == reported.RestartCount && - this.LastRestartTimeUtc == reported.LastRestartTimeUtc && - this.RuntimeStatus == reported.RuntimeStatus; + this.ExitCode == reported.ExitCode && + string.Equals(this.StatusDescription, reported.StatusDescription) && + this.LastStartTimeUtc == reported.LastStartTimeUtc && + this.LastExitTimeUtc == reported.LastExitTimeUtc && + this.RestartCount == reported.RestartCount && + this.LastRestartTimeUtc == reported.LastRestartTimeUtc && + this.RuntimeStatus == reported.RuntimeStatus; } else { return base.Equals(other); } - } public override int GetHashCode() @@ -92,10 +101,19 @@ public override int GetHashCode() } public IModule WithRuntimeStatus(ModuleStatus newStatus) => new TestRuntimeModule( - this.Name, this.Version, this.RestartPolicy, this.Type, - this.DesiredStatus, this.Config, this.ExitCode, - this.StatusDescription, this.LastStartTimeUtc, - this.LastExitTimeUtc, this.RestartCount, this.LastRestartTimeUtc, - newStatus, this.ConfigurationInfo); + this.Name, + this.Version, + this.RestartPolicy, + this.Type, + this.DesiredStatus, + this.Config, + this.ExitCode, + this.StatusDescription, + this.LastStartTimeUtc, + this.LastExitTimeUtc, + this.RestartCount, + this.LastRestartTimeUtc, + newStatus, + this.ConfigurationInfo); } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModuleTest.cs index 25d7623b52c..60f3f88fc06 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/TestRuntimeModuleTest.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; - using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -12,16 +11,14 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test public class TestRuntimeModuleTest { static readonly TestConfig Config1 = new TestConfig("image1"); - static readonly TestConfig Config2 = new TestConfig("image2"); - static readonly TestConfig Config3 = new TestConfig("image1"); - public static TestModule TestModule1 = new TestModule("name", "version", "type", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, new ConfigurationInfo("1"), null); - static DateTime lastStartTime = DateTime.Parse("2017-08-04T17:52:13.0419502Z", null, DateTimeStyles.RoundtripKind); - static DateTime lastExitTime = lastStartTime.AddDays(1); - + static readonly DateTime lastStartTime = DateTime.Parse("2017-08-04T17:52:13.0419502Z", null, DateTimeStyles.RoundtripKind); + static readonly DateTime lastExitTime = lastStartTime.AddDays(1); public static TestRuntimeModule ReportedModule1 = new TestRuntimeModule("name", "version", RestartPolicy.OnUnhealthy, "type", ModuleStatus.Running, Config1, 0, "statusDescription", lastStartTime, lastExitTime, 0, DateTime.MinValue, ModuleStatus.Running); + static readonly TestConfig Config2 = new TestConfig("image2"); public static TestRuntimeModule ReportedModule2 = new TestRuntimeModule("name", "version", RestartPolicy.OnUnhealthy, "type", ModuleStatus.Running, Config2, 0, "statusDescription", lastStartTime, lastExitTime, 0, DateTime.MinValue, ModuleStatus.Running); + static readonly TestConfig Config3 = new TestConfig("image1"); public static TestRuntimeModule ReportedModule3 = new TestRuntimeModule("name", "version", RestartPolicy.OnUnhealthy, "type", ModuleStatus.Running, Config3, 0, "statusDescription", lastStartTime, lastExitTime, 0, DateTime.MinValue, ModuleStatus.Running); public static TestRuntimeModule ReportedModule4 = new TestRuntimeModule("name", "version", RestartPolicy.OnUnhealthy, "type", ModuleStatus.Running, Config3, -1, "statusDescription", lastStartTime, lastExitTime, 0, DateTime.MinValue, ModuleStatus.Running); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/UpstreamProtocolTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/UpstreamProtocolTest.cs index 4ce0b55767b..af6437c5270 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/UpstreamProtocolTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/UpstreamProtocolTest.cs @@ -11,7 +11,7 @@ public class UpstreamProtocolTest public static IEnumerable UpstreamProtocolInputs() { yield return new object[] { null, Option.None() }; - yield return new object[] { "", Option.None() }; + yield return new object[] { string.Empty, Option.None() }; yield return new object[] { " ", Option.None() }; yield return new object[] { "Amqp", Option.Some(UpstreamProtocol.Amqp) }; yield return new object[] { "AmqpWs", Option.Some(UpstreamProtocol.AmqpWs) }; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/GroupCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/GroupCommandTest.cs index 4e0cbe4b115..5a5a1fea242 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/GroupCommandTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/GroupCommandTest.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands +namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.Commands { using System; using System.Collections.Generic; @@ -14,6 +14,89 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands public class GroupCommandTest { + [Fact] + [Unit] + public void CreateThrowsOnNull() + { + Assert.Throws(() => new GroupCommand(null)); + } + + [Theory] + [Unit] + [MemberData(nameof(CreateTestData))] + public async Task TestCreate( + Option recorder, + List moduleExecutionList, + List commandList) + { + var g = new GroupCommand(commandList.ToArray()); + + var token = new CancellationToken(); + + await g.ExecuteAsync(token); + + this.AssertCommands(recorder, commandList, moduleExecutionList); + } + + [Theory] + [Unit] + [MemberData(nameof(CreateTestData))] + public async Task TestUndoAsync( + Option recorder, + List moduleExecutionList, + List commandList) + { + ICommand g = new GroupCommand(commandList.ToArray()); + + var token = new CancellationToken(); + + await g.UndoAsync(token); + + this.AssertUndo(recorder, commandList, moduleExecutionList); + } + + [Theory] + [Unit] + [MemberData(nameof(CreateTestData))] + public void TestShow( + Option recorder, + List moduleExecutionList, + List commandList) + { + ICommand g = new GroupCommand(commandList.ToArray()); + + string showString = g.Show(); + + foreach (ICommand command in commandList) + { + Assert.True(showString.Contains(command.Show())); + } + } + + [Fact] + [Unit] + public async Task TestGroupCommandCancellation() + { + // Arrange + var cts = new CancellationTokenSource(); + Mock[] commands = + { + this.MakeMockCommand("c1"), + this.MakeMockCommand("c2", () => cts.Cancel()), + this.MakeMockCommand("c3"), + }; + ICommand groupCommand = new GroupCommand( + commands.Select(m => m.Object).ToArray()); + + // Act + await groupCommand.ExecuteAsync(cts.Token); + + // Assert + commands[0].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); + commands[1].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); + commands[2].Verify(m => m.ExecuteAsync(cts.Token), Times.Never()); + } + static IEnumerable CreateTestData() { var defaultConfigurationInfo = new ConfigurationInfo(); @@ -29,7 +112,8 @@ static IEnumerable CreateTestData() TestCommandType.TestCreate, TestCommandType.TestCreate }; - var tm = new List{ + var tm = new List + { new TestModule("module1", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image1"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), new TestModule("module2", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image2"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), new TestModule("module3", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image3"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), @@ -67,9 +151,9 @@ static IEnumerable CreateTestData() } ), ( - recordKeeper3, - new List(), - new List() + recordKeeper3, + new List(), + new List() ) }; { @@ -77,23 +161,18 @@ static IEnumerable CreateTestData() } } - [Fact] - [Unit] - public void CreateThrowsOnNull() - { - Assert.Throws(() => new GroupCommand(null)); - } - // Disabling this reSharper Error. commandList is being used on Assert All. // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local void AssertCommands(Option recordKeeper, List commandList, List recordlist) { - Assert.All(commandList, command => - { - var c = command as TestCommand; - Assert.NotNull(c); - Assert.True(c.CommandExecuted); - }); + Assert.All( + commandList, + command => + { + var c = command as TestCommand; + Assert.NotNull(c); + Assert.True(c.CommandExecuted); + }); recordKeeper.ForEach(r => Assert.Equal(recordlist, r.ExecutionList)); } @@ -101,70 +180,17 @@ void AssertCommands(Option recordKeeper, List comman // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local void AssertUndo(Option recordKeeper, List commandList, List recordlist) { - Assert.All(commandList, command => - { - var c = command as TestCommand; - Assert.NotNull(c); - Assert.True(c.CommandUndone); - }); + Assert.All( + commandList, + command => + { + var c = command as TestCommand; + Assert.NotNull(c); + Assert.True(c.CommandUndone); + }); recordKeeper.ForEach(r => Assert.Equal(recordlist, r.UndoList)); } - [Theory] - [Unit] - [MemberData(nameof(CreateTestData))] - public async Task TestCreate( - Option recorder, - List moduleExecutionList, - List commandList - ) - { - var g = new GroupCommand(commandList.ToArray()); - - var token = new CancellationToken(); - - await g.ExecuteAsync(token); - - this.AssertCommands(recorder, commandList, moduleExecutionList); - } - - [Theory] - [Unit] - [MemberData(nameof(CreateTestData))] - public async Task TestUndoAsync( - Option recorder, - List moduleExecutionList, - List commandList - ) - { - ICommand g = new GroupCommand(commandList.ToArray()); - - var token = new CancellationToken(); - - await g.UndoAsync(token); - - this.AssertUndo(recorder, commandList, moduleExecutionList); - } - - [Theory] - [Unit] - [MemberData(nameof(CreateTestData))] - public void TestShow( - Option recorder, - List moduleExecutionList, - List commandList - ) - { - ICommand g = new GroupCommand(commandList.ToArray()); - - string showString = g.Show(); - - foreach (ICommand command in commandList) - { - Assert.True(showString.Contains(command.Show())); - } - } - Mock MakeMockCommand(string id, Action callback = null) { callback = callback ?? (() => { }); @@ -178,30 +204,5 @@ Mock MakeMockCommand(string id, Action callback = null) .Returns(id); return command; } - - [Fact] - [Unit] - public async Task TestGroupCommandCancellation() - { - // Arrange - var cts = new CancellationTokenSource(); - Mock[] commands = - { - this.MakeMockCommand("c1"), - this.MakeMockCommand("c2", () => cts.Cancel()), - this.MakeMockCommand("c3"), - }; - ICommand groupCommand = new GroupCommand( - commands.Select(m => m.Object).ToArray() - ); - - // Act - await groupCommand.ExecuteAsync(cts.Token); - - // Assert - commands[0].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); - commands[1].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); - commands[2].Verify(m => m.ExecuteAsync(cts.Token), Times.Never()); - } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/LoggingCommandFactoryTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/LoggingCommandFactoryTest.cs index bfe22799dd8..5085bd15428 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/LoggingCommandFactoryTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/LoggingCommandFactoryTest.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands +namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.Commands { using System; using System.Collections.Generic; @@ -7,21 +7,21 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Agent.Core.Commands; - using Moq; - using Xunit; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Extensions.Logging; + using Moq; + using Xunit; using CommandMethodExpr = System.Linq.Expressions.Expression>>; using TestExecutionExpr = System.Func>; class FailureCommand : ICommand { - public static FailureCommand Instance { get; } = new FailureCommand(); - FailureCommand() { } + public static FailureCommand Instance { get; } = new FailureCommand(); + public string Id => this.Show(); public Task ExecuteAsync(CancellationToken token) => throw new ArgumentException(); @@ -39,7 +39,6 @@ public class LoggingCommandFactoryTest static readonly TestModule UpdateModule = new TestModule("module", "version", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); static readonly TestCommand WrapTargetCommand = new TestCommand(TestCommandType.TestCreate, TestModule); - [Fact] [Unit] public void InvalidParamtersForConstructor() @@ -69,58 +68,6 @@ public async void TestShow() ICommand create = await factory.CreateAsync(new ModuleWithIdentity(TestModule, moduleIdentity.Object), runtimeInfo); Assert.Equal(create.Show(), nullCmd.Result.Show()); - - } - - static IEnumerable CreateTestData() - { - var moduleIdentity = new Mock(); - var testModule = new ModuleWithIdentity(TestModule, moduleIdentity.Object); - var updateModule = new ModuleWithIdentity(UpdateModule, moduleIdentity.Object); - var runtimeInfo = Mock.Of(); - // CommandMethodBeingTested - factory command under test - // Command - command object to be mocked. - // TestExpr - the expression to execute test. - (CommandMethodExpr CommandMethodBeingTested, Task Command, TestExecutionExpr TestExpr)[] testInputRecords = { - ( - f => f.CreateAsync(testModule, runtimeInfo), - NullCommandFactory.Instance.CreateAsync(testModule, runtimeInfo), - factory => factory.CreateAsync(testModule, runtimeInfo) - ), - ( - f => f.UpdateAsync(TestModule, updateModule, runtimeInfo), - NullCommandFactory.Instance.UpdateAsync(TestModule, updateModule, runtimeInfo), - factory => factory.UpdateAsync(TestModule, updateModule, runtimeInfo) - ), - ( - f => f.RemoveAsync(TestModule), - NullCommandFactory.Instance.RemoveAsync(TestModule), - factory => factory.RemoveAsync(TestModule) - ), - ( - f => f.StartAsync(TestModule), - NullCommandFactory.Instance.StartAsync(TestModule), - factory => factory.StartAsync(TestModule) - ), - ( - f => f.StopAsync(TestModule), - NullCommandFactory.Instance.StopAsync(TestModule), - factory => factory.StopAsync(TestModule) - ), - - ( - f => f.RestartAsync(TestModule), - NullCommandFactory.Instance.RestartAsync(TestModule), - factory => factory.RestartAsync(TestModule) - ), - ( - f => f.WrapAsync(WrapTargetCommand), - Task.FromResult(WrapTargetCommand), - factory => factory.WrapAsync(WrapTargetCommand) - ) - }; - - return testInputRecords.Select(r => new object[] { r.CommandMethodBeingTested, r.Command, r.TestExpr }).AsEnumerable(); } [Theory] @@ -129,8 +76,7 @@ static IEnumerable CreateTestData() public async Task ExecuteSuccessfulTests( CommandMethodExpr commandMethodBeingTested, Task commandBeingDecorated, - TestExecutionExpr testExpr - ) + TestExecutionExpr testExpr) { var token = new CancellationToken(); @@ -154,7 +100,7 @@ TestExecutionExpr testExpr // attempt to execute the LoggingCommand we received await create.ExecuteAsync(token); - //Assert decorated command is executed, and command is logged. + // Assert decorated command is executed, and command is logged. factoryMock.Verify(commandMethodBeingTested); logMock.Verify(l => l.Log(LogLevel.Information, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); logMock.Verify(l => l.Log(LogLevel.Debug, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); @@ -166,8 +112,7 @@ TestExecutionExpr testExpr public async Task ExecuteFailureTests( CommandMethodExpr commandMethodBeingTested, Task commandBeingDecorated, - TestExecutionExpr testExpr - ) + TestExecutionExpr testExpr) { var token = new CancellationToken(); @@ -197,8 +142,7 @@ TestExecutionExpr testExpr public async Task UndoSuccessTests( CommandMethodExpr commandMethodBeingTested, Task commandBeingDecorated, - TestExecutionExpr testExpr - ) + TestExecutionExpr testExpr) { var token = new CancellationToken(); @@ -228,8 +172,7 @@ TestExecutionExpr testExpr public async Task UndoFailureTests( CommandMethodExpr commandMethodBeingTested, Task commandBeingDecorated, - TestExecutionExpr testExpr - ) + TestExecutionExpr testExpr) { var token = new CancellationToken(); @@ -252,5 +195,57 @@ TestExecutionExpr testExpr logMock.Verify(l => l.Log(LogLevel.Information, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); logMock.Verify(l => l.Log(LogLevel.Error, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); } + + static IEnumerable CreateTestData() + { + var moduleIdentity = new Mock(); + var testModule = new ModuleWithIdentity(TestModule, moduleIdentity.Object); + var updateModule = new ModuleWithIdentity(UpdateModule, moduleIdentity.Object); + var runtimeInfo = Mock.Of(); + // CommandMethodBeingTested - factory command under test + // Command - command object to be mocked. + // TestExpr - the expression to execute test. + (CommandMethodExpr CommandMethodBeingTested, Task Command, TestExecutionExpr TestExpr)[] testInputRecords = + { + ( + f => f.CreateAsync(testModule, runtimeInfo), + NullCommandFactory.Instance.CreateAsync(testModule, runtimeInfo), + factory => factory.CreateAsync(testModule, runtimeInfo) + ), + ( + f => f.UpdateAsync(TestModule, updateModule, runtimeInfo), + NullCommandFactory.Instance.UpdateAsync(TestModule, updateModule, runtimeInfo), + factory => factory.UpdateAsync(TestModule, updateModule, runtimeInfo) + ), + ( + f => f.RemoveAsync(TestModule), + NullCommandFactory.Instance.RemoveAsync(TestModule), + factory => factory.RemoveAsync(TestModule) + ), + ( + f => f.StartAsync(TestModule), + NullCommandFactory.Instance.StartAsync(TestModule), + factory => factory.StartAsync(TestModule) + ), + ( + f => f.StopAsync(TestModule), + NullCommandFactory.Instance.StopAsync(TestModule), + factory => factory.StopAsync(TestModule) + ), + + ( + f => f.RestartAsync(TestModule), + NullCommandFactory.Instance.RestartAsync(TestModule), + factory => factory.RestartAsync(TestModule) + ), + ( + f => f.WrapAsync(WrapTargetCommand), + Task.FromResult(WrapTargetCommand), + factory => factory.WrapAsync(WrapTargetCommand) + ) + }; + + return testInputRecords.Select(r => new object[] { r.CommandMethodBeingTested, r.Command, r.TestExpr }).AsEnumerable(); + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandFactoryTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandFactoryTest.cs index bb78dfbde36..6ed90a6739d 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandFactoryTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandFactoryTest.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands +namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.Commands { using System.Collections.Generic; using Microsoft.Azure.Devices.Edge.Agent.Core.Commands; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandTest.cs index c80a3bfae12..dd3b3df1ddb 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/NullCommandTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands +// Copyright (c) Microsoft. All rights reserved. +namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.Commands { using System.Threading; using Microsoft.Azure.Devices.Edge.Agent.Core.Commands; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/ParallelGroupCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/ParallelGroupCommandTest.cs index 35ba302cfb7..ac8e1579343 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/ParallelGroupCommandTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/commands/ParallelGroupCommandTest.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands +namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.Commands { using System; using System.Collections.Generic; @@ -14,71 +14,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.commands public class ParallelGroupCommandTest { - static IEnumerable CreateTestData() - { - var defaultConfigurationInfo = new ConfigurationInfo(); - IDictionary envVars = new Dictionary(); - Option recordKeeper1 = Option.Some(new TestPlanRecorder()); - Option recordKeeper2 = Option.Some(new TestPlanRecorder()); - Option recordKeeper3 = Option.Some(new TestPlanRecorder()); - var tt = new List - { - TestCommandType.TestCreate, - TestCommandType.TestCreate, - TestCommandType.TestCreate, - TestCommandType.TestCreate, - TestCommandType.TestCreate - }; - - var tm = new List{ - new TestModule("module1", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image1"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), - new TestModule("module2", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image2"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), - new TestModule("module3", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image3"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), - new TestModule("module4", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image4"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), - new TestModule("module5", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image5"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars) - }; - - (Option recorder, List moduleExecutionList, List commandList)[] testInputRecords = - { - ( - recordKeeper1, - new List - { - new TestRecordType(tt[0], tm[0]) - }, - new List - { - new TestCommand(tt[0], tm[0], recordKeeper1) - } - ), - ( - recordKeeper2, - new List - { - new TestRecordType(tt[1], tm[1]), - new TestRecordType(tt[2], tm[2]), - new TestRecordType(tt[3], tm[3]), - new TestRecordType(tt[4], tm[4]) - }, - new List - { - new TestCommand(tt[1], tm[1], recordKeeper2), - new TestCommand(tt[2], tm[2], recordKeeper2), - new TestCommand(tt[3], tm[3], recordKeeper2), - new TestCommand(tt[4], tm[4], recordKeeper2) - } - ), - ( - recordKeeper3, - new List(), - new List() - ) - }; - { - return testInputRecords.Select(r => new object[] { r.recorder, r.moduleExecutionList, r.commandList }).AsEnumerable(); - } - } - [Fact] [Unit] public void CreateThrowsOnNull() @@ -86,40 +21,13 @@ public void CreateThrowsOnNull() Assert.Throws(() => new ParallelGroupCommand(null)); } - // Disabling this reSharper Error. commandList is being used on Assert All. - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - void AssertCommands(Option recordKeeper, List commandList, List recordlist) - { - Assert.All(commandList, command => - { - var c = command as TestCommand; - Assert.NotNull(c); - Assert.True(c.CommandExecuted); - }); - recordKeeper.ForEach(r => Assert.Equal(recordlist, r.ExecutionList)); - } - - // Disabling this reSharper Error. commandList is being used on Assert All. - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - void AssertUndo(Option recordKeeper, List commandList, List recordlist) - { - Assert.All(commandList, command => - { - var c = command as TestCommand; - Assert.NotNull(c); - Assert.True(c.CommandUndone); - }); - recordKeeper.ForEach(r => Assert.Equal(recordlist, r.UndoList)); - } - [Theory] [Unit] [MemberData(nameof(CreateTestData))] public async Task TestCreate( Option recorder, List moduleExecutionList, - List commandList - ) + List commandList) { var g = new ParallelGroupCommand(commandList.ToArray()); @@ -136,8 +44,7 @@ List commandList public async Task TestUndoAsync( Option recorder, List moduleExecutionList, - List commandList - ) + List commandList) { ICommand g = new ParallelGroupCommand(commandList.ToArray()); @@ -154,8 +61,7 @@ List commandList public void TestShow( Option recorder, List moduleExecutionList, - List commandList - ) + List commandList) { ICommand g = new ParallelGroupCommand(commandList.ToArray()); @@ -167,20 +73,6 @@ List commandList } } - Mock MakeMockCommand(string id, Action callback = null) - { - callback = callback ?? (() => { }); - - var command = new Mock(); - command.SetupGet(c => c.Id).Returns(id); - command.Setup(c => c.ExecuteAsync(It.IsAny())) - .Callback(callback) - .Returns(Task.CompletedTask); - command.Setup(c => c.Show()) - .Returns(id); - return command; - } - [Fact] [Unit] public async Task TestParallelGroupCommandCancellation() @@ -194,8 +86,7 @@ public async Task TestParallelGroupCommandCancellation() this.MakeMockCommand("c3"), }; ICommand groupCommand = new ParallelGroupCommand( - commands.Select(m => m.Object).ToArray() - ); + commands.Select(m => m.Object).ToArray()); // Act await groupCommand.ExecuteAsync(cts.Token); @@ -206,19 +97,6 @@ public async Task TestParallelGroupCommandCancellation() commands[2].Verify(m => m.ExecuteAsync(cts.Token), Times.Once()); } - Mock MakeDelayingCommand(string id, TimeSpan delay) - { - var command = new Mock(); - command.SetupGet(c => c.Id).Returns(id); - command.Setup(c => c.ExecuteAsync(It.IsAny())) - .Returns(Task.Delay(delay)); - command.Setup(c => c.UndoAsync(It.IsAny())) - .Returns(Task.Delay(delay)); - command.Setup(c => c.Show()) - .Returns(id); - return command; - } - [Fact] [Unit] public async Task TestParallelGroupCommandExecution() @@ -234,8 +112,7 @@ public async Task TestParallelGroupCommandExecution() }; ICommand groupCommand = new ParallelGroupCommand( - commands.Select(m => m.Object).ToArray() - ); + commands.Select(m => m.Object).ToArray()); // Act Task executeTask = groupCommand.ExecuteAsync(cts.Token); @@ -264,8 +141,7 @@ public async Task TestParallelGroupCommandUndo() }; ICommand groupCommand = new ParallelGroupCommand( - commands.Select(m => m.Object).ToArray() - ); + commands.Select(m => m.Object).ToArray()); // Act Task executeTask = groupCommand.UndoAsync(cts.Token); @@ -278,5 +154,128 @@ public async Task TestParallelGroupCommandUndo() commands[1].Verify(m => m.UndoAsync(cts.Token), Times.Once()); commands[2].Verify(m => m.UndoAsync(cts.Token), Times.Once()); } + + static IEnumerable CreateTestData() + { + var defaultConfigurationInfo = new ConfigurationInfo(); + IDictionary envVars = new Dictionary(); + Option recordKeeper1 = Option.Some(new TestPlanRecorder()); + Option recordKeeper2 = Option.Some(new TestPlanRecorder()); + Option recordKeeper3 = Option.Some(new TestPlanRecorder()); + var tt = new List + { + TestCommandType.TestCreate, + TestCommandType.TestCreate, + TestCommandType.TestCreate, + TestCommandType.TestCreate, + TestCommandType.TestCreate + }; + + var tm = new List + { + new TestModule("module1", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image1"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), + new TestModule("module2", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image2"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), + new TestModule("module3", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image3"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), + new TestModule("module4", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image4"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars), + new TestModule("module5", "version1", "type1", ModuleStatus.Stopped, new TestConfig("image5"), RestartPolicy.OnUnhealthy, defaultConfigurationInfo, envVars) + }; + + (Option recorder, List moduleExecutionList, List commandList)[] testInputRecords = + { + ( + recordKeeper1, + new List + { + new TestRecordType(tt[0], tm[0]) + }, + new List + { + new TestCommand(tt[0], tm[0], recordKeeper1) + } + ), + ( + recordKeeper2, + new List + { + new TestRecordType(tt[1], tm[1]), + new TestRecordType(tt[2], tm[2]), + new TestRecordType(tt[3], tm[3]), + new TestRecordType(tt[4], tm[4]) + }, + new List + { + new TestCommand(tt[1], tm[1], recordKeeper2), + new TestCommand(tt[2], tm[2], recordKeeper2), + new TestCommand(tt[3], tm[3], recordKeeper2), + new TestCommand(tt[4], tm[4], recordKeeper2) + } + ), + ( + recordKeeper3, + new List(), + new List() + ) + }; + { + return testInputRecords.Select(r => new object[] { r.recorder, r.moduleExecutionList, r.commandList }).AsEnumerable(); + } + } + + // Disabling this reSharper Error. commandList is being used on Assert All. + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + void AssertCommands(Option recordKeeper, List commandList, List recordlist) + { + Assert.All( + commandList, + command => + { + var c = command as TestCommand; + Assert.NotNull(c); + Assert.True(c.CommandExecuted); + }); + recordKeeper.ForEach(r => Assert.Equal(recordlist, r.ExecutionList)); + } + + // Disabling this reSharper Error. commandList is being used on Assert All. + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + void AssertUndo(Option recordKeeper, List commandList, List recordlist) + { + Assert.All( + commandList, + command => + { + var c = command as TestCommand; + Assert.NotNull(c); + Assert.True(c.CommandUndone); + }); + recordKeeper.ForEach(r => Assert.Equal(recordlist, r.UndoList)); + } + + Mock MakeMockCommand(string id, Action callback = null) + { + callback = callback ?? (() => { }); + + var command = new Mock(); + command.SetupGet(c => c.Id).Returns(id); + command.Setup(c => c.ExecuteAsync(It.IsAny())) + .Callback(callback) + .Returns(Task.CompletedTask); + command.Setup(c => c.Show()) + .Returns(id); + return command; + } + + Mock MakeDelayingCommand(string id, TimeSpan delay) + { + var command = new Mock(); + command.SetupGet(c => c.Id).Returns(id); + command.Setup(c => c.ExecuteAsync(It.IsAny())) + .Returns(Task.Delay(delay)); + command.Setup(c => c.UndoAsync(It.IsAny())) + .Returns(Task.Delay(delay)); + command.Setup(c => c.Show()) + .Returns(id); + return command; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileBackupConfigSourceTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileBackupConfigSourceTest.cs index 637c9a11446..039048461cc 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileBackupConfigSourceTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileBackupConfigSourceTest.cs @@ -63,7 +63,7 @@ public void InvalidInputsFails() { var underlying = new Mock(); - Assert.Throws(() => new FileBackupConfigSource("", underlying.Object, this.GetSerde(), NullEncryptionProvider.Instance)); + Assert.Throws(() => new FileBackupConfigSource(string.Empty, underlying.Object, this.GetSerde(), NullEncryptionProvider.Instance)); Assert.Throws(() => new FileBackupConfigSource(null, underlying.Object, this.GetSerde(), NullEncryptionProvider.Instance)); Assert.Throws(() => new FileBackupConfigSource(this.tempFileName, null, this.GetSerde(), NullEncryptionProvider.Instance)); Assert.Throws(() => new FileBackupConfigSource(this.tempFileName, underlying.Object, null, NullEncryptionProvider.Instance)); @@ -256,7 +256,7 @@ public async void FileBackupDoesNotThrowWhenEncryptFails() ISerde serde = this.GetSerde(); var encryptionProvider = new Mock(); encryptionProvider.Setup(ep => ep.EncryptAsync(It.IsAny())) - .ThrowsAsync(new IoTEdgedException("failed", 404, "", null, null)); + .ThrowsAsync(new IoTEdgedException("failed", 404, string.Empty, null, null)); using (IConfigSource configSource = new FileBackupConfigSource(this.tempFileName, underlying.Object, serde, encryptionProvider.Object)) { DeploymentConfigInfo config1 = await configSource.GetDeploymentConfigInfoAsync(); @@ -332,7 +332,7 @@ public async void FileBackupShouldNotThrowWhenDecryptFails() encryptionProvider.Setup(ep => ep.EncryptAsync(It.IsAny())) .ReturnsAsync(serde.Serialize(ValidConfigInfo1)); encryptionProvider.Setup(ep => ep.DecryptAsync(It.IsAny())) - .ThrowsAsync(new IoTEdgedException("failed", 404, "", null, null)); + .ThrowsAsync(new IoTEdgedException("failed", 404, string.Empty, null, null)); using (IConfigSource configSource = new FileBackupConfigSource(this.tempFileName, underlying.Object, serde, encryptionProvider.Object)) { diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileConfigSourceTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileConfigSourceTest.cs index 61604d7b221..70e14d57381 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileConfigSourceTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/configsources/FileConfigSourceTest.cs @@ -14,24 +14,149 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Core.Test.ConfigSources public class FileConfigSourceTest : IDisposable { const string TestType = "test"; + + const string ValidJson1 = @"{ + ""version"": 0, + ""deploymentConfig"": { + ""schemaVersion"": ""1.0"", + ""runtime"": { + ""type"": ""test"", + ""settings"": { + ""minDockerVersion"": ""v1.13"", + ""loggingOptions"": """" + } + }, + ""systemModules"": { + ""edgeAgent"": { + ""type"": ""test"", + ""settings"": { + ""image"": ""edge-agent"" + }, + ""configuration"": { + ""id"": ""1234"" + } + }, + ""edgeHub"": { + ""type"": ""test"", + ""status"": ""running"", + ""restartPolicy"": ""always"", + ""settings"": { + ""image"": ""edge-hub:latest"" + }, + ""configuration"": { + ""id"": ""1234"" + } + } + }, + ""modules"": { + ""mod1"": { + ""version"": ""version1"", + ""type"": ""test"", + ""status"": ""running"", + ""restartPolicy"": ""on-unhealthy"", + ""settings"": { + ""image"": ""image1"" + }, + ""configuration"": { + ""id"": ""1234"" + } + } + } + } + }"; + + const string ValidJson2 = @"{ + ""version"": 0, + ""deploymentConfig"": { + ""schemaVersion"": ""1.0"", + ""runtime"": { + ""type"": ""test"", + ""settings"": { + ""minDockerVersion"": ""v1.13"", + ""loggingOptions"": """" + } + }, + ""systemModules"": { + ""edgeAgent"": { + ""type"": ""test"", + ""settings"": { + ""image"": ""edge-agent"" + }, + ""configuration"": { + ""id"": ""1234"" + } + }, + ""edgeHub"": { + ""type"": ""test"", + ""status"": ""running"", + ""restartPolicy"": ""always"", + ""settings"": { + ""image"": ""edge-hub:latest"" + }, + ""configuration"": { + ""id"": ""1234"" + } + } + }, + ""modules"": { + ""mod1"": { + ""version"": ""version1"", + ""type"": ""test"", + ""status"": ""stopped"", + ""restartPolicy"": ""on-unhealthy"", + ""settings"": { + ""image"": ""image1"" + }, + ""configuration"": { + ""id"": ""1234"" + } + }, + ""mod2"": { + ""version"": ""version1"", + ""type"": ""test"", + ""status"": ""running"", + ""restartPolicy"": ""on-unhealthy"", + ""settings"": { + ""image"": ""image1"" + }, + ""configuration"": { + ""id"": ""1234"" + } + } + } + } + }"; + static readonly IDictionary EnvVars = new Dictionary(); + static readonly ConfigurationInfo ConfigurationInfo = new ConfigurationInfo(); + static readonly TestConfig Config1 = new TestConfig("image1"); + static readonly IModule ValidModule1 = new TestModule("mod1", "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, ConfigurationInfo, EnvVars); + static readonly IEdgeHubModule EdgeHubModule = new TestHubModule("edgeHub", "test", ModuleStatus.Running, new TestConfig("edge-hub:latest"), RestartPolicy.Always, ConfigurationInfo, EnvVars); + static readonly IEdgeAgentModule EdgeAgentModule = new TestAgentModule("edgeAgent", "test", new TestConfig("edge-agent"), ConfigurationInfo, null); + static readonly IDictionary Modules1 = new Dictionary { ["mod1"] = ValidModule1 }; + static readonly ModuleSet ValidSet1 = new ModuleSet(new Dictionary(Modules1) { [EdgeHubModule.Name] = EdgeHubModule, [EdgeAgentModule.Name] = EdgeAgentModule }); static readonly IModule UpdatedModule1 = new TestModule("mod1", "version1", "test", ModuleStatus.Stopped, Config1, RestartPolicy.OnUnhealthy, ConfigurationInfo, EnvVars); + static readonly IModule ValidModule2 = new TestModule("mod2", "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, ConfigurationInfo, EnvVars); + static readonly IDictionary Modules2 = new Dictionary { ["mod1"] = UpdatedModule1, ["mod2"] = ValidModule2 }; + static readonly ModuleSet ValidSet2 = new ModuleSet(new Dictionary(Modules2) { [EdgeHubModule.Name] = EdgeHubModule, [EdgeAgentModule.Name] = EdgeAgentModule }); static readonly string InvalidJson1 = "{\"This is a terrible string\"}"; readonly string tempFileName; + readonly IConfigurationRoot config; + readonly ISerde serde; public FileConfigSourceTest() @@ -125,7 +250,7 @@ public async void ChangeFileAndSeeChange() Assert.False(newDiff.IsEmpty); Assert.Equal(newDiff, validDiff1To2); } - } + } [Fact] [Unit] @@ -167,118 +292,6 @@ public async void ChangeFileToInvalidBackToOk() Assert.Equal(newDiff, validDiff1To2); } } - - const string ValidJson1 = @"{ - ""version"": 0, - ""deploymentConfig"": { - ""schemaVersion"": ""1.0"", - ""runtime"": { - ""type"": ""test"", - ""settings"": { - ""minDockerVersion"": ""v1.13"", - ""loggingOptions"": """" - } - }, - ""systemModules"": { - ""edgeAgent"": { - ""type"": ""test"", - ""settings"": { - ""image"": ""edge-agent"" - }, - ""configuration"": { - ""id"": ""1234"" - } - }, - ""edgeHub"": { - ""type"": ""test"", - ""status"": ""running"", - ""restartPolicy"": ""always"", - ""settings"": { - ""image"": ""edge-hub:latest"" - }, - ""configuration"": { - ""id"": ""1234"" - } - } - }, - ""modules"": { - ""mod1"": { - ""version"": ""version1"", - ""type"": ""test"", - ""status"": ""running"", - ""restartPolicy"": ""on-unhealthy"", - ""settings"": { - ""image"": ""image1"" - }, - ""configuration"": { - ""id"": ""1234"" - } - } - } - } - }"; - - const string ValidJson2 = @"{ - ""version"": 0, - ""deploymentConfig"": { - ""schemaVersion"": ""1.0"", - ""runtime"": { - ""type"": ""test"", - ""settings"": { - ""minDockerVersion"": ""v1.13"", - ""loggingOptions"": """" - } - }, - ""systemModules"": { - ""edgeAgent"": { - ""type"": ""test"", - ""settings"": { - ""image"": ""edge-agent"" - }, - ""configuration"": { - ""id"": ""1234"" - } - }, - ""edgeHub"": { - ""type"": ""test"", - ""status"": ""running"", - ""restartPolicy"": ""always"", - ""settings"": { - ""image"": ""edge-hub:latest"" - }, - ""configuration"": { - ""id"": ""1234"" - } - } - }, - ""modules"": { - ""mod1"": { - ""version"": ""version1"", - ""type"": ""test"", - ""status"": ""stopped"", - ""restartPolicy"": ""on-unhealthy"", - ""settings"": { - ""image"": ""image1"" - }, - ""configuration"": { - ""id"": ""1234"" - } - }, - ""mod2"": { - ""version"": ""version1"", - ""type"": ""test"", - ""status"": ""running"", - ""restartPolicy"": ""on-unhealthy"", - ""settings"": { - ""image"": ""image1"" - }, - ""configuration"": { - ""id"": ""1234"" - } - } - } - } - }"; } class TestRuntimeInfo : IRuntimeInfo @@ -290,8 +303,12 @@ public TestRuntimeInfo(string type) public string Type { get; } + public static bool operator ==(TestRuntimeInfo left, TestRuntimeInfo right) => Equals(left, right); + + public static bool operator !=(TestRuntimeInfo left, TestRuntimeInfo right) => !Equals(left, right); + public bool Equals(IRuntimeInfo other) => other is TestRuntimeInfo otherRuntimeInfo - && this.Equals(otherRuntimeInfo); + && this.Equals(otherRuntimeInfo); public override bool Equals(object obj) { @@ -301,25 +318,27 @@ public override bool Equals(object obj) return true; if (obj.GetType() != this.GetType()) return false; - return Equals((TestRuntimeInfo)obj); + return this.Equals((TestRuntimeInfo)obj); } public override int GetHashCode() { - return (this.Type != null ? this.Type.GetHashCode() : 0); + return this.Type != null ? this.Type.GetHashCode() : 0; } public bool Equals(TestRuntimeInfo other) { if (ReferenceEquals(null, other)) + { return false; + } + if (ReferenceEquals(this, other)) + { return true; + } + return string.Equals(this.Type, other.Type); } - - public static bool operator ==(TestRuntimeInfo left, TestRuntimeInfo right) => Equals(left, right); - - public static bool operator !=(TestRuntimeInfo left, TestRuntimeInfo right) => !Equals(left, right); } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/HealthRestartPlannerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/HealthRestartPlannerTest.cs index c6c7f4d1f09..e387722ad89 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/HealthRestartPlannerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/HealthRestartPlannerTest.cs @@ -25,16 +25,6 @@ public class HealthRestartPlannerTest static readonly TestConfig Config1 = new TestConfig("image1"); static readonly TestConfig Config2 = new TestConfig("image2"); - static (TestCommandFactory factory, Mock> store, IRestartPolicyManager restartManager, HealthRestartPlanner planner) CreatePlanner() - { - var factory = new TestCommandFactory(); - var store = new Mock>(); - var restartManager = new RestartPolicyManager(MaxRestartCount, CoolOffTimeUnitInSeconds); - var planner = new HealthRestartPlanner(factory, store.Object, IntensiveCareTime, restartManager); - - return (factory, store, restartManager, planner); - } - [Fact] [Unit] public void TestCreateValidation() @@ -113,8 +103,19 @@ public async void TestUpdateModule() (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); IRuntimeModule currentModule = new TestRuntimeModule( - "mod1", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running); + "mod1", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running); IModule desiredModule = new TestModule("mod1", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); IImmutableDictionary moduleIdentities = GetModuleIdentities(new List() { desiredModule }); ModuleSet currentSet = ModuleSet.Create(currentModule); @@ -140,8 +141,19 @@ public async void TestRemoveModule() (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); IRuntimeModule removeModule = new TestRuntimeModule( - "mod1", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running); + "mod1", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running); ModuleSet removeRunning = ModuleSet.Create(removeModule); var removeExecutionList = new List { @@ -152,84 +164,14 @@ public async void TestRemoveModule() var planRunner = new OrderedPlanRunner(); await planRunner.ExecuteAsync(1, addPlan, CancellationToken.None); - factory.Recorder.ForEach(r => - { - Assert.Equal(removeExecutionList, r.ExecutionList); - Assert.Equal(1, r.WrappedCommmandList.Count); - }); + factory.Recorder.ForEach( + r => + { + Assert.Equal(removeExecutionList, r.ExecutionList); + Assert.Equal(1, r.WrappedCommmandList.Count); + }); } - static IRuntimeModule[] GetRemoveTestData() => new IRuntimeModule[] - { - // Always - new TestRuntimeModule( - "removeModule1", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running), - new TestRuntimeModule( - "removeModule2", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), - new TestRuntimeModule( - "removeModule3", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), - new TestRuntimeModule( - "removeModule4", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), - new TestRuntimeModule( - "removeModule5", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), - - // OnUnhealthy - new TestRuntimeModule( - "removeModule6", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running), - new TestRuntimeModule( - "removeModule7", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), - new TestRuntimeModule( - "removeModule8", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), - new TestRuntimeModule( - "removeModule9", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), - new TestRuntimeModule( - "removeModule10", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), - - // OnFailure - new TestRuntimeModule( - "removeModule11", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running), - new TestRuntimeModule( - "removeModule12", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), - new TestRuntimeModule( - "removeModule13", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), - new TestRuntimeModule( - "removeModule14", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), - new TestRuntimeModule( - "removeModule15", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), - - // Never - new TestRuntimeModule( - "removeModule16", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running), - new TestRuntimeModule( - "removeModule17", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), - new TestRuntimeModule( - "removeModule18", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), - new TestRuntimeModule( - "removeModule19", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), - new TestRuntimeModule( - "removeModule20", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed) - }; - [Fact] [Unit] public async Task TestRemoveKitchenSink() @@ -239,153 +181,24 @@ public async Task TestRemoveKitchenSink() IRuntimeModule[] removedModules = GetRemoveTestData(); ModuleSet removeRunning = ModuleSet.Create(removedModules.ToArray()); - List expectedExecutionList = removedModules.SelectMany(m => new[] - { - new TestRecordType(TestCommandType.TestStop, m), - new TestRecordType(TestCommandType.TestRemove, m) - }).ToList(); + List expectedExecutionList = removedModules.SelectMany( + m => new[] + { + new TestRecordType(TestCommandType.TestStop, m), + new TestRecordType(TestCommandType.TestRemove, m) + }).ToList(); Plan addPlan = await planner.PlanAsync(ModuleSet.Empty, removeRunning, RuntimeInfo, ImmutableDictionary.Empty); var planRunner = new OrderedPlanRunner(); await planRunner.ExecuteAsync(1, addPlan, CancellationToken.None); - factory.Recorder.ForEach(r => - { - Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count()); - Assert.Equal(removedModules.Count(), r.WrappedCommmandList.Count); - }); + factory.Recorder.ForEach( + r => + { + Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count()); + Assert.Equal(removedModules.Count(), r.WrappedCommmandList.Count); + }); } - static (IRuntimeModule RunningModule, IModule UpdatedModule)[] GetUpdateDeployTestData() => new(IRuntimeModule RunningModule, IModule UpdatedModule)[] - { - // Always - ( - new TestRuntimeModule( - "updateDeployModule1", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running, null, EnvVars), - new TestModule("updateDeployModule1", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule2", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff, null, EnvVars), - new TestModule("updateDeployModule2", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule3", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy, null, EnvVars), - new TestModule("updateDeployModule3", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule4", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped, null, EnvVars), - new TestModule("updateDeployModule4", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule5", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed, null, EnvVars), - new TestModule("updateDeployModule5", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) - ), - - // OnUnhealthy - ( - new TestRuntimeModule( - "updateDeployModule6", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running, null, EnvVars), - new TestModule("updateDeployModule6", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule7", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff, null, EnvVars), - new TestModule("updateDeployModule7", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule8", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy, null, EnvVars), - new TestModule("updateDeployModule8", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule9", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped, null, EnvVars), - new TestModule("updateDeployModule9", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule10", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed, null, EnvVars), - new TestModule("updateDeployModule10", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) - ), - - // OnFailure - ( - new TestRuntimeModule( - "updateDeployModule11", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running, null, EnvVars), - new TestModule("updateDeployModule11", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule12", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff, null, EnvVars), - new TestModule("updateDeployModule12", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule13", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy, null, EnvVars), - new TestModule("updateDeployModule13", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule14", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped, null, EnvVars), - new TestModule("updateDeployModule14", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule15", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed, null, EnvVars), - new TestModule("updateDeployModule15", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) - ), - - // Never - ( - new TestRuntimeModule( - "updateDeployModule16", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Running, null, EnvVars), - new TestModule("updateDeployModule16", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule17", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff, null, EnvVars), - new TestModule("updateDeployModule17", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule18", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy, null, EnvVars), - new TestModule("updateDeployModule18", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule19", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped, null, EnvVars), - new TestModule("updateDeployModule19", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) - ), - ( - new TestRuntimeModule( - "updateDeployModule20", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed, null, EnvVars), - new TestModule("updateDeployModule20", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) - ), - }; - [Fact] [Unit] public async Task TestUpdateDeployKitchenSink() @@ -403,12 +216,140 @@ public async Task TestUpdateDeployKitchenSink() ModuleSet desiredModuleSet = ModuleSet.Create(data.Select(d => d.UpdatedModule).ToArray()); // build expected execution list - IEnumerable expectedExecutionList = data.SelectMany(d => new[] - { - new TestRecordType(TestCommandType.TestStop, d.UpdatedModule), - new TestRecordType(TestCommandType.TestUpdate, d.UpdatedModule), - new TestRecordType(TestCommandType.TestStart, d.UpdatedModule) - }); + IEnumerable expectedExecutionList = data.SelectMany( + d => new[] + { + new TestRecordType(TestCommandType.TestStop, d.UpdatedModule), + new TestRecordType(TestCommandType.TestUpdate, d.UpdatedModule), + new TestRecordType(TestCommandType.TestStart, d.UpdatedModule) + }); + + // Act + Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, moduleIdentities); + var planRunner = new OrderedPlanRunner(); + await planRunner.ExecuteAsync(1, plan, CancellationToken.None); + + // Assert + factory.Recorder.ForEach( + r => + { + Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count()); + Assert.Equal(data.Length * 2, r.WrappedCommmandList.Count); + }); + } + + [Fact] + [Unit] + public async Task TestUpdateStateChangedKitchenSink() + { + // Arrange + (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); + + // prepare list of modules whose configurations have been updated + (IRuntimeModule RunningModule, IModule UpdatedModule)[] updateDeployModules = GetUpdateDeployTestData(); + + // prepare list of removed modules + IEnumerable removedModules = GetRemoveTestData(); + + // prepare a list of existing modules whose runtime status may/may not have been updated + (IRuntimeModule RunningModule, bool Restart)[] updateStateChangedModules = GetUpdateStateChangeTestData(); + + // build "current" and "desired" module sets + ModuleSet currentModuleSet = ModuleSet.Create( + updateDeployModules + .Select(d => d.RunningModule) + .Concat(removedModules) + .Concat(updateStateChangedModules.Select(m => m.RunningModule)) + .ToArray()); + ModuleSet desiredModuleSet = ModuleSet.Create( + updateDeployModules + .Select(d => d.UpdatedModule) + .Concat(updateStateChangedModules.Select(m => m.RunningModule)) + .ToArray()); + IImmutableDictionary moduleIdentities = GetModuleIdentities(updateDeployModules.Select(d => d.UpdatedModule).ToList()); + + // build expected execution list + IEnumerable expectedExecutionList = updateDeployModules + .SelectMany( + d => new[] + { + new TestRecordType(TestCommandType.TestStop, d.UpdatedModule), + new TestRecordType(TestCommandType.TestUpdate, d.UpdatedModule), + new TestRecordType(TestCommandType.TestStart, d.UpdatedModule) + }) + .Concat( + removedModules.SelectMany( + m => new[] + { + new TestRecordType(TestCommandType.TestStop, m), + new TestRecordType(TestCommandType.TestRemove, m) + })) + .Concat( + updateStateChangedModules + .Where(d => d.Restart) + .SelectMany( + d => new[] + { + new TestRecordType(TestCommandType.TestStop, d.RunningModule), + new TestRecordType(TestCommandType.TestStart, d.RunningModule) + })); + + // Act + Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, moduleIdentities); + var planRunner = new OrderedPlanRunner(); + await planRunner.ExecuteAsync(1, plan, CancellationToken.None); + + // Assert + factory.Recorder.ForEach(r => Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count())); + } + + [Fact] + [Unit] + public async Task TestUpdateStateChanged_Offline_NoIdentities() + { + // Arrange + (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); + + // prepare list of modules whose configurations have been updated + (IRuntimeModule RunningModule, IModule UpdatedModule)[] updateDeployModules = GetUpdateDeployTestData(); + + // prepare list of removed modules + IEnumerable removedModules = GetRemoveTestData(); + + // prepare a list of existing modules whose runtime status may/may not have been updated + (IRuntimeModule RunningModule, bool Restart)[] updateStateChangedModules = GetUpdateStateChangeTestData(); + + // build "current" and "desired" module sets + ModuleSet currentModuleSet = ModuleSet.Create( + updateDeployModules + .Select(d => d.RunningModule) + .Concat(removedModules) + .Concat(updateStateChangedModules.Select(m => m.RunningModule)) + .ToArray()); + ModuleSet desiredModuleSet = ModuleSet.Create( + updateDeployModules + .Select(d => d.UpdatedModule) + .Concat(updateStateChangedModules.Select(m => m.RunningModule)) + .ToArray()); + IImmutableDictionary moduleIdentities = ImmutableDictionary.Empty; + + // build expected execution list + IEnumerable expectedExecutionList = removedModules + .SelectMany( + m => new[] + { + new TestRecordType(TestCommandType.TestStop, m), + new TestRecordType(TestCommandType.TestRemove, m) + }) + .Concat( + updateStateChangedModules + .Where(d => d.Restart) + .SelectMany( + d => new[] + { + new TestRecordType(TestCommandType.TestStop, d.RunningModule), + new TestRecordType(TestCommandType.TestStart, d.RunningModule) + })); // Act Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, moduleIdentities); @@ -416,14 +357,763 @@ public async Task TestUpdateDeployKitchenSink() await planRunner.ExecuteAsync(1, plan, CancellationToken.None); // Assert - factory.Recorder.ForEach(r => + factory.Recorder.ForEach(r => Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count())); + } + + [Fact] + [Unit] + public async Task TestResetStatsForHealthyModules() + { + // Arrange + (TestCommandFactory factory, Mock> store, _, HealthRestartPlanner planner) = CreatePlanner(); + + // derive list of "running great" modules from GetUpdateStateChangeTestData() + IList runningGreatModules = GetUpdateStateChangeTestData() + .Where(d => d.Restart == false) + .Select(d => d.RunningModule) + .Where(m => m.DesiredStatus == ModuleStatus.Running && m.RuntimeStatus == ModuleStatus.Running) + .ToList(); + + // have the "store" return true when the "Contains" call happens to check if a module has + // records in the store with stats + store.Setup(s => s.Contains(It.IsAny())) + .Returns(() => Task.FromResult(true)); + + ModuleSet currentModuleSet = ModuleSet.Create(runningGreatModules.ToArray()); + ModuleSet desiredModuleSet = ModuleSet.Create(runningGreatModules.ToArray()); + + // Act + Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, ImmutableDictionary.Empty); + var planRunner = new OrderedPlanRunner(); + await planRunner.ExecuteAsync(1, plan, CancellationToken.None); + + // Assert + factory.Recorder.ForEach(r => Assert.Equal(runningGreatModules.Count(), r.WrappedCommmandList.Count)); + } + + [Unit] + [Fact] + public async Task CreateShutdownPlanTest() + { + // Arrange + (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); + + IModule module1 = new TestModule("mod1", "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); + IModule edgeAgentModule = new TestModule(Constants.EdgeAgentModuleName, "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); + var modules = new List { - Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count()); - Assert.Equal(data.Length * 2, r.WrappedCommmandList.Count); - }); + module1, + edgeAgentModule + }; + + ModuleSet running = ModuleSet.Create(modules.ToArray()); + var executionList = new List + { + new TestRecordType(TestCommandType.TestStop, module1), + }; + + // Act + Plan shutdownPlan = await planner.CreateShutdownPlanAsync(running); + var planRunner = new OrderedPlanRunner(); + await planRunner.ExecuteAsync(1, shutdownPlan, CancellationToken.None); + + // Assert + factory.Recorder.ForEach(r => Assert.Equal(executionList, r.ExecutionList)); } - static (IRuntimeModule RunningModule, bool Restart)[] GetUpdateStateChangeTestData() => new(IRuntimeModule RunningModule, bool Restart)[] + static (TestCommandFactory factory, Mock> store, IRestartPolicyManager restartManager, HealthRestartPlanner planner) CreatePlanner() + { + var factory = new TestCommandFactory(); + var store = new Mock>(); + var restartManager = new RestartPolicyManager(MaxRestartCount, CoolOffTimeUnitInSeconds); + var planner = new HealthRestartPlanner(factory, store.Object, IntensiveCareTime, restartManager); + + return (factory, store, restartManager, planner); + } + + static IRuntimeModule[] GetRemoveTestData() => new IRuntimeModule[] + { + // Always + new TestRuntimeModule( + "removeModule1", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), + new TestRuntimeModule( + "removeModule2", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), + new TestRuntimeModule( + "removeModule3", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), + new TestRuntimeModule( + "removeModule4", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), + new TestRuntimeModule( + "removeModule5", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), + + // OnUnhealthy + new TestRuntimeModule( + "removeModule6", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), + new TestRuntimeModule( + "removeModule7", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), + new TestRuntimeModule( + "removeModule8", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), + new TestRuntimeModule( + "removeModule9", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), + new TestRuntimeModule( + "removeModule10", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), + + // OnFailure + new TestRuntimeModule( + "removeModule11", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), + new TestRuntimeModule( + "removeModule12", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), + new TestRuntimeModule( + "removeModule13", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), + new TestRuntimeModule( + "removeModule14", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), + new TestRuntimeModule( + "removeModule15", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), + + // Never + new TestRuntimeModule( + "removeModule16", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), + new TestRuntimeModule( + "removeModule17", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), + new TestRuntimeModule( + "removeModule18", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), + new TestRuntimeModule( + "removeModule19", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), + new TestRuntimeModule( + "removeModule20", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed) + }; + + static (IRuntimeModule RunningModule, IModule UpdatedModule)[] GetUpdateDeployTestData() => new (IRuntimeModule RunningModule, IModule UpdatedModule)[] + { + // Always + ( + new TestRuntimeModule( + "updateDeployModule1", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + null, + EnvVars), + new TestModule("updateDeployModule1", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule2", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff, + null, + EnvVars), + new TestModule("updateDeployModule2", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule3", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy, + null, + EnvVars), + new TestModule("updateDeployModule3", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule4", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped, + null, + EnvVars), + new TestModule("updateDeployModule4", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule5", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed, + null, + EnvVars), + new TestModule("updateDeployModule5", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Always, DefaultConfigurationInfo, EnvVars) + ), + + // OnUnhealthy + ( + new TestRuntimeModule( + "updateDeployModule6", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + null, + EnvVars), + new TestModule("updateDeployModule6", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule7", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff, + null, + EnvVars), + new TestModule("updateDeployModule7", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule8", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy, + null, + EnvVars), + new TestModule("updateDeployModule8", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule9", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped, + null, + EnvVars), + new TestModule("updateDeployModule9", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule10", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed, + null, + EnvVars), + new TestModule("updateDeployModule10", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars) + ), + + // OnFailure + ( + new TestRuntimeModule( + "updateDeployModule11", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + null, + EnvVars), + new TestModule("updateDeployModule11", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule12", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff, + null, + EnvVars), + new TestModule("updateDeployModule12", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule13", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy, + null, + EnvVars), + new TestModule("updateDeployModule13", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule14", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped, + null, + EnvVars), + new TestModule("updateDeployModule14", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule15", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed, + null, + EnvVars), + new TestModule("updateDeployModule15", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.OnFailure, DefaultConfigurationInfo, EnvVars) + ), + + // Never + ( + new TestRuntimeModule( + "updateDeployModule16", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + null, + EnvVars), + new TestModule("updateDeployModule16", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule17", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff, + null, + EnvVars), + new TestModule("updateDeployModule17", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule18", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy, + null, + EnvVars), + new TestModule("updateDeployModule18", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule19", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped, + null, + EnvVars), + new TestModule("updateDeployModule19", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) + ), + ( + new TestRuntimeModule( + "updateDeployModule20", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed, + null, + EnvVars), + new TestModule("updateDeployModule20", "version1", "test", ModuleStatus.Running, Config2, RestartPolicy.Never, DefaultConfigurationInfo, EnvVars) + ), + }; + + static (IRuntimeModule RunningModule, bool Restart)[] GetUpdateStateChangeTestData() => new (IRuntimeModule RunningModule, bool Restart)[] { /////////////////////////// // RestartPolicy.Always @@ -432,89 +1122,231 @@ public async Task TestUpdateDeployKitchenSink() // ModuleStatus.Running ( new TestRuntimeModule( - "updateStateModule1", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), 0, - DateTime.MinValue, ModuleStatus.Running), + "updateStateModule1", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), + 0, + DateTime.MinValue, + ModuleStatus.Running), false ), // ModuleStatus.Backoff ( new TestRuntimeModule( - "updateStateModule2", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule2", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), true ), ( new TestRuntimeModule( - "updateStateModule3", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule3", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Backoff), true ), ( new TestRuntimeModule( - "updateStateModule4", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule4", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Backoff), false ), // ModuleStatus.Unhealthy ( new TestRuntimeModule( - "updateStateModule5", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule5", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule6", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule6", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule7", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule7", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), // ModuleStatus.Stopped ( new TestRuntimeModule( - "updateStateModule8", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule8", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule9", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule9", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule10", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule10", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Stopped), false ), // ModuleStatus.Failed ( new TestRuntimeModule( - "updateStateModule11", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule11", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule12", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule12", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule13", "version1", RestartPolicy.Always, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule13", + "version1", + RestartPolicy.Always, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Failed), false ), @@ -525,89 +1357,231 @@ public async Task TestUpdateDeployKitchenSink() // ModuleStatus.Running ( new TestRuntimeModule( - "updateStateModule14", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), 0, - DateTime.MinValue, ModuleStatus.Running), + "updateStateModule14", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), + 0, + DateTime.MinValue, + ModuleStatus.Running), false ), // ModuleStatus.Backoff ( new TestRuntimeModule( - "updateStateModule15", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule15", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), true ), ( new TestRuntimeModule( - "updateStateModule16", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule16", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Backoff), true ), ( new TestRuntimeModule( - "updateStateModule17", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule17", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Backoff), false ), // ModuleStatus.Unhealthy ( new TestRuntimeModule( - "updateStateModule18", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule18", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule19", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule19", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule20", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule20", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), // ModuleStatus.Stopped ( new TestRuntimeModule( - "updateStateModule21", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule21", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule22", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule22", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule23", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule23", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Stopped), false ), // ModuleStatus.Failed ( new TestRuntimeModule( - "updateStateModule24", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule24", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule25", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule25", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule26", "version1", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule26", + "version1", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Failed), false ), @@ -618,89 +1592,231 @@ public async Task TestUpdateDeployKitchenSink() // ModuleStatus.Running ( new TestRuntimeModule( - "updateStateModule27", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), 0, - DateTime.MinValue, ModuleStatus.Running), + "updateStateModule27", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), + 0, + DateTime.MinValue, + ModuleStatus.Running), false ), // ModuleStatus.Backoff ( new TestRuntimeModule( - "updateStateModule28", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule28", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), true ), ( new TestRuntimeModule( - "updateStateModule29", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule29", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Backoff), true ), ( new TestRuntimeModule( - "updateStateModule30", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule30", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Backoff), false ), // ModuleStatus.Unhealthy ( new TestRuntimeModule( - "updateStateModule31", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule31", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule32", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule32", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule33", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule33", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), // ModuleStatus.Stopped ( new TestRuntimeModule( - "updateStateModule34", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule34", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule35", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule35", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule36", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule36", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Stopped), false ), // ModuleStatus.Failed ( new TestRuntimeModule( - "updateStateModule37", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule37", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule38", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule38", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule39", "version1", RestartPolicy.OnFailure, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule39", + "version1", + RestartPolicy.OnFailure, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Failed), false ), @@ -711,272 +1827,235 @@ public async Task TestUpdateDeployKitchenSink() // ModuleStatus.Running ( new TestRuntimeModule( - "updateStateModule40", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), 0, - DateTime.MinValue, ModuleStatus.Running), + "updateStateModule40", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - IntensiveCareTime - TimeSpan.FromMinutes(5), + 0, + DateTime.MinValue, + ModuleStatus.Running), false ), // ModuleStatus.Backoff ( new TestRuntimeModule( - "updateStateModule41", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule41", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), false ), ( new TestRuntimeModule( - "updateStateModule42", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule42", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Backoff), false ), ( new TestRuntimeModule( - "updateStateModule43", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Backoff), + "updateStateModule43", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Backoff), false ), // ModuleStatus.Unhealthy ( new TestRuntimeModule( - "updateStateModule44", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule44", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule45", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule45", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), ( new TestRuntimeModule( - "updateStateModule46", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Unhealthy), + "updateStateModule46", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Unhealthy), false ), // ModuleStatus.Stopped ( new TestRuntimeModule( - "updateStateModule47", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule47", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule48", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule48", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Stopped), false ), ( new TestRuntimeModule( - "updateStateModule49", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Stopped), + "updateStateModule49", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Stopped), false ), // ModuleStatus.Failed ( new TestRuntimeModule( - "updateStateModule50", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.MinValue, 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule50", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule51", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromHours(1), 0, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule51", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromHours(1), + 0, + DateTime.MinValue, + ModuleStatus.Failed), false ), ( new TestRuntimeModule( - "updateStateModule52", "version1", RestartPolicy.Never, "test", ModuleStatus.Running, Config1, - 0, string.Empty, DateTime.MinValue, DateTime.UtcNow - TimeSpan.FromSeconds(40), 3, DateTime.MinValue, ModuleStatus.Failed), + "updateStateModule52", + "version1", + RestartPolicy.Never, + "test", + ModuleStatus.Running, + Config1, + 0, + string.Empty, + DateTime.MinValue, + DateTime.UtcNow - TimeSpan.FromSeconds(40), + 3, + DateTime.MinValue, + ModuleStatus.Failed), false ), }; - [Fact] - [Unit] - public async Task TestUpdateStateChangedKitchenSink() - { - // Arrange - (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); - - // prepare list of modules whose configurations have been updated - (IRuntimeModule RunningModule, IModule UpdatedModule)[] updateDeployModules = GetUpdateDeployTestData(); - - // prepare list of removed modules - IEnumerable removedModules = GetRemoveTestData(); - - // prepare a list of existing modules whose runtime status may/may not have been updated - (IRuntimeModule RunningModule, bool Restart)[] updateStateChangedModules = GetUpdateStateChangeTestData(); - - // build "current" and "desired" module sets - ModuleSet currentModuleSet = ModuleSet.Create(updateDeployModules - .Select(d => d.RunningModule) - .Concat(removedModules) - .Concat(updateStateChangedModules.Select(m => m.RunningModule)) - .ToArray() - ); - ModuleSet desiredModuleSet = ModuleSet.Create(updateDeployModules - .Select(d => d.UpdatedModule) - .Concat(updateStateChangedModules.Select(m => m.RunningModule)) - .ToArray() - ); - IImmutableDictionary moduleIdentities = GetModuleIdentities(updateDeployModules.Select(d => d.UpdatedModule).ToList()); - - // build expected execution list - IEnumerable expectedExecutionList = updateDeployModules - .SelectMany(d => new[] - { - new TestRecordType(TestCommandType.TestStop, d.UpdatedModule), - new TestRecordType(TestCommandType.TestUpdate, d.UpdatedModule), - new TestRecordType(TestCommandType.TestStart, d.UpdatedModule) - }) - .Concat(removedModules.SelectMany(m => new[] - { - new TestRecordType(TestCommandType.TestStop, m), - new TestRecordType(TestCommandType.TestRemove, m) - })) - .Concat( - updateStateChangedModules - .Where(d => d.Restart) - .SelectMany(d => new[] - { - new TestRecordType(TestCommandType.TestStop, d.RunningModule), - new TestRecordType(TestCommandType.TestStart, d.RunningModule) - }) - ); - - // Act - Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, moduleIdentities); - var planRunner = new OrderedPlanRunner(); - await planRunner.ExecuteAsync(1, plan, CancellationToken.None); - - // Assert - factory.Recorder.ForEach(r => Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count())); - } - - [Fact] - [Unit] - public async Task TestUpdateStateChanged_Offline_NoIdentities() - { - // Arrange - (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); - - // prepare list of modules whose configurations have been updated - (IRuntimeModule RunningModule, IModule UpdatedModule)[] updateDeployModules = GetUpdateDeployTestData(); - - // prepare list of removed modules - IEnumerable removedModules = GetRemoveTestData(); - - // prepare a list of existing modules whose runtime status may/may not have been updated - (IRuntimeModule RunningModule, bool Restart)[] updateStateChangedModules = GetUpdateStateChangeTestData(); - - // build "current" and "desired" module sets - ModuleSet currentModuleSet = ModuleSet.Create(updateDeployModules - .Select(d => d.RunningModule) - .Concat(removedModules) - .Concat(updateStateChangedModules.Select(m => m.RunningModule)) - .ToArray() - ); - ModuleSet desiredModuleSet = ModuleSet.Create(updateDeployModules - .Select(d => d.UpdatedModule) - .Concat(updateStateChangedModules.Select(m => m.RunningModule)) - .ToArray() - ); - IImmutableDictionary moduleIdentities = ImmutableDictionary.Empty; - - // build expected execution list - IEnumerable expectedExecutionList = removedModules - .SelectMany(m => new[] - { - new TestRecordType(TestCommandType.TestStop, m), - new TestRecordType(TestCommandType.TestRemove, m) - }) - .Concat( - updateStateChangedModules - .Where(d => d.Restart) - .SelectMany(d => new[] - { - new TestRecordType(TestCommandType.TestStop, d.RunningModule), - new TestRecordType(TestCommandType.TestStart, d.RunningModule) - }) - ); - - // Act - Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, moduleIdentities); - var planRunner = new OrderedPlanRunner(); - await planRunner.ExecuteAsync(1, plan, CancellationToken.None); - - // Assert - factory.Recorder.ForEach(r => Assert.Equal(0, expectedExecutionList.Except(r.ExecutionList).Count())); - } - - [Fact] - [Unit] - public async Task TestResetStatsForHealthyModules() - { - // Arrange - (TestCommandFactory factory, Mock> store, _, HealthRestartPlanner planner) = CreatePlanner(); - - // derive list of "running great" modules from GetUpdateStateChangeTestData() - IList runningGreatModules = GetUpdateStateChangeTestData() - .Where(d => d.Restart == false) - .Select(d => d.RunningModule) - .Where(m => m.DesiredStatus == ModuleStatus.Running && m.RuntimeStatus == ModuleStatus.Running) - .ToList(); - - // have the "store" return true when the "Contains" call happens to check if a module has - // records in the store with stats - store.Setup(s => s.Contains(It.IsAny())) - .Returns(() => Task.FromResult(true)); - - ModuleSet currentModuleSet = ModuleSet.Create(runningGreatModules.ToArray()); - ModuleSet desiredModuleSet = ModuleSet.Create(runningGreatModules.ToArray()); - - // Act - Plan plan = await planner.PlanAsync(desiredModuleSet, currentModuleSet, RuntimeInfo, ImmutableDictionary.Empty); - var planRunner = new OrderedPlanRunner(); - await planRunner.ExecuteAsync(1, plan, CancellationToken.None); - - // Assert - factory.Recorder.ForEach(r => Assert.Equal(runningGreatModules.Count(), r.WrappedCommmandList.Count)); - } - - [Unit] - [Fact] - public async Task CreateShutdownPlanTest() - { - // Arrange - (TestCommandFactory factory, _, _, HealthRestartPlanner planner) = CreatePlanner(); - - IModule module1 = new TestModule("mod1", "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); - IModule edgeAgentModule = new TestModule(Constants.EdgeAgentModuleName, "version1", "test", ModuleStatus.Running, Config1, RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); - var modules = new List - { - module1, - edgeAgentModule - }; - - ModuleSet running = ModuleSet.Create(modules.ToArray()); - var executionList = new List - { - new TestRecordType(TestCommandType.TestStop, module1), - }; - - // Act - Plan shutdownPlan = await planner.CreateShutdownPlanAsync(running); - var planRunner = new OrderedPlanRunner(); - await planRunner.ExecuteAsync(1, shutdownPlan, CancellationToken.None); - - // Assert - factory.Recorder.ForEach(r => Assert.Equal(executionList, r.ExecutionList)); - } - static IImmutableDictionary GetModuleIdentities(IList modules) { ICredentials credential = new ConnectionStringCredentials("fake"); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/RestartPlannerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/RestartPlannerTest.cs index b66aca98605..c4a16228f1a 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/RestartPlannerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Core.Test/planners/RestartPlannerTest.cs @@ -182,7 +182,7 @@ public async void RestartPlannerAddRemoveUpdate() var planRunner = new OrderedPlanRunner(); await planRunner.ExecuteAsync(1, addPlan, token); - //Weak confirmation: no assumed order. + // Weak confirmation: no assumed order. factory.Recorder.ForEach(recorder => Assert.All(updateExecutionList, r => Assert.True(recorder.ExecutionList.Contains(r)))); factory.Recorder.ForEach( recorder => diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/AgentTests.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/AgentTests.cs index 2fc2017a210..edee7bf926c 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/AgentTests.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/AgentTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test using global::Docker.DotNet; using global::Docker.DotNet.Models; using Microsoft.Azure.Devices.Edge.Agent.Core; - using Microsoft.Azure.Devices.Edge.Agent.Core.ConfigSources; using Microsoft.Azure.Devices.Edge.Agent.Core.Planners; using Microsoft.Azure.Devices.Edge.Agent.Core.PlanRunners; using Microsoft.Azure.Devices.Edge.Agent.Core.Reporters; @@ -28,6 +27,55 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test public class AgentTests { + public static IEnumerable GenerateStartTestData() + { + IEnumerable testsToRun = ConfigHelper.TestConfig.GetSection("testSuite").GetChildren(); + + // Each test in the test suite supports the notion of a "validator". We determine what + // validator to use by looking at the "$type" property in the test configuration JSON. + // Here's an example: + // + // { + // "name": "mongo-server", + // "version": "1.0", + // "image": "mongo:3.4.4", + // "imageCreateOptions": "{\"HostConfig\": {\"PortBindings\": {\"80/tcp\": [{\"HostPort\": \"8080\"}]}}}", + // "validator": { + // "$type": "RunCommandValidator", + // "command": "docker", + // "args": "run --rm --link mongo-server:mongo-server mongo:3.4.4 sh -c \"exec mongo --quiet --eval 'db.serverStatus().version' mongo-server:27017/test\"", + // "outputEquals": "3.4.4" + // } + // } + // + // Here the value "RunCommandValidator" for "$type" means that Newtonsoft JSON will + // de-serialize the "validator" object from the JSON into an instance of type "RunCommandValidator". + // We provide the mapping from the value of "$type" to a fully qualified .NET type name by providing + // a "serialization binder" - in our case this is an instance of TypeNameSerializationBinder. The JSON + // deserializer consults the TypeNameSerializationBinder instance to determine what type to instantiate. + Type agentTestsType = typeof(AgentTests); + string format = $"{agentTestsType.Namespace}.{{0}}, {agentTestsType.Assembly.GetName().Name}"; + var settings = new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.Auto, + SerializationBinder = new TypeNameSerializationBinder(format) + }; + + // appSettings.json contains an array property called "testSuite" which is a list of strings + // containing names of JSON files that contain the spec for the tests to run. We process all the + // JSON files and build a flat list of tests (instances of TestConfig). + IEnumerable result = testsToRun.SelectMany( + cs => + { + string json = File.ReadAllText(cs.Value); + return JsonConvert + .DeserializeObject(json, settings) + .Select(config => new object[] { config }); + }); + + return result; + } + [Integration] [Theory] [MemberData(nameof(GenerateStartTestData))] @@ -56,27 +104,27 @@ public async Task AgentStartsUpModules(TestConfig testConfig) testConfig.Name, testConfig.Version, ModuleStatus.Running, - Core.RestartPolicy.OnUnhealthy, + global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, dockerConfig, null, - null - ); + null); var modules = new Dictionary { [testConfig.Name] = dockerModule }; var systemModules = new SystemModules(null, null); // Start up the agent and run a "reconcile". var dockerLoggingOptions = new Dictionary { - {"max-size", "1m"}, - {"max-file", "1" } + { "max-size", "1m" }, + { "max-file", "1" } }; var loggingConfig = new DockerLoggingConfig("json-file", dockerLoggingOptions); string sharedAccessKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("test")); - IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary - { - { "DeviceConnectionString", $"Hostname=fakeiothub;Deviceid=test;SharedAccessKey={sharedAccessKey}" } - }).Build(); + IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( + new Dictionary + { + { "DeviceConnectionString", $"Hostname=fakeiothub;Deviceid=test;SharedAccessKey={sharedAccessKey}" } + }).Build(); var runtimeConfig = new DockerRuntimeConfig("1.24.0", "{}"); var runtimeInfo = new DockerRuntimeInfo("docker", runtimeConfig); @@ -111,8 +159,16 @@ public async Task AgentStartsUpModules(TestConfig testConfig) var moduleIdentityLifecycleManager = new Mock(); moduleIdentityLifecycleManager.Setup(m => m.GetModuleIdentitiesAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(identities)); - Agent agent = await Agent.Create(configSource.Object, new RestartPlanner(commandFactory), new OrderedPlanRunner(), reporter, - moduleIdentityLifecycleManager.Object, environmentProvider, configStore, deploymentConfigInfoSerde, NullEncryptionProvider.Instance); + Agent agent = await Agent.Create( + configSource.Object, + new RestartPlanner(commandFactory), + new OrderedPlanRunner(), + reporter, + moduleIdentityLifecycleManager.Object, + environmentProvider, + configStore, + deploymentConfigInfoSerde, + NullEncryptionProvider.Instance); await agent.ReconcileAsync(CancellationToken.None); // Sometimes the container is still not ready by the time we run the validator. @@ -127,6 +183,7 @@ public async Task AgentStartsUpModules(TestConfig testConfig) { Thread.Sleep(TimeSpan.FromSeconds(5)); } + ++attempts; } @@ -138,7 +195,7 @@ public async Task AgentStartsUpModules(TestConfig testConfig) } } - private static async Task RemoveContainer(IDockerClient client, TestConfig testConfig) + static async Task RemoveContainer(IDockerClient client, TestConfig testConfig) { // get current list of containers (running or otherwise) where their name // matches what's given in the test settings @@ -157,56 +214,5 @@ private static async Task RemoveContainer(IDockerClient client, TestConfig testC }; await Task.WhenAll(toBeRemoved.Select(c => client.Containers.RemoveContainerAsync(c.ID, removeParams))); } - - public static IEnumerable GenerateStartTestData() - { - IEnumerable testsToRun = ConfigHelper.TestConfig.GetSection("testSuite").GetChildren(); - - // Each test in the test suite supports the notion of a "validator". We determine what - // validator to use by looking at the "$type" property in the test configuration JSON. - // Here's an example: - // - // { - // "name": "mongo-server", - // "version": "1.0", - // "image": "mongo:3.4.4", - // "imageCreateOptions": "{\"HostConfig\": {\"PortBindings\": {\"80/tcp\": [{\"HostPort\": \"8080\"}]}}}", - // "validator": { - // "$type": "RunCommandValidator", - // "command": "docker", - // "args": "run --rm --link mongo-server:mongo-server mongo:3.4.4 sh -c \"exec mongo --quiet --eval 'db.serverStatus().version' mongo-server:27017/test\"", - // "outputEquals": "3.4.4" - // } - // } - // - // Here the value "RunCommandValidator" for "$type" means that Newtonsoft JSON will - // de-serialize the "validator" object from the JSON into an instance of type "RunCommandValidator". - // We provide the mapping from the value of "$type" to a fully qualified .NET type name by providing - // a "serialization binder" - in our case this is an instance of TypeNameSerializationBinder. The JSON - // deserializer consults the TypeNameSerializationBinder instance to determine what type to instantiate. - - Type agentTestsType = typeof(AgentTests); - string format = $"{agentTestsType.Namespace}.{{0}}, {agentTestsType.Assembly.GetName().Name}"; - var settings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.Auto, - SerializationBinder = new TypeNameSerializationBinder(format) - }; - - // appSettings.json contains an array property called "testSuite" which is a list of strings - // containing names of JSON files that contain the spec for the tests to run. We process all the - // JSON files and build a flat list of tests (instances of TestConfig). - - IEnumerable result = testsToRun.SelectMany( - cs => - { - string json = File.ReadAllText(cs.Value); - return JsonConvert - .DeserializeObject(json, settings) - .Select(config => new object[] { config }); - }); - - return result; - } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test.csproj index 5a540511653..a66b6b62421 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test.csproj +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test.csproj @@ -45,4 +45,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/RunCommandValidator.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/RunCommandValidator.cs index e2c429fbf3f..97f2b157a7c 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/RunCommandValidator.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test/RunCommandValidator.cs @@ -5,17 +5,17 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.E2E.Test public class RunCommandValidator : Validator { + public RunCommandValidator() + { + this.Type = ValidatorType.RunCommand; + } + public string Command { get; set; } public string Args { get; set; } public string OutputEquals { get; set; } - public RunCommandValidator() - { - this.Type = ValidatorType.RunCommand; - } - public override bool Validate() { var process = new Process @@ -28,7 +28,7 @@ public override bool Validate() } }; - string output = ""; + string output = string.Empty; process.OutputDataReceived += (sender, args) => output += args.Data; process.Start(); process.BeginOutputReadLine(); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerConfigTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerConfigTest.cs index 8c53125223b..53af2cdc725 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerConfigTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerConfigTest.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test { using System.Diagnostics.CodeAnalysis; using System.Linq; - using global::Docker.DotNet.Models; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Newtonsoft.Json; using Xunit; @@ -118,8 +117,8 @@ public void TestDeserializationError() public void TestSerialization() { var createOptions = @"{""Env"": [""k1=v1"", ""k2=v2"", ""k3=v3""], ""HostConfig"": {""PortBindings"": {""43/udp"": [" + - string.Join(", ", Enumerable.Repeat(@"{""HostPort"": ""43""}", 50)) + - @"], ""42/tcp"": [{""HostPort"": ""42""}]}}}"; + string.Join(", ", Enumerable.Repeat(@"{""HostPort"": ""43""}", 50)) + + @"], ""42/tcp"": [{""HostPort"": ""42""}]}}}"; var config = new DockerConfig("image1:42", createOptions); var json = JsonConvert.SerializeObject(config); var expected = "{\"image\":\"image1:42\",\"createOptions\":\"{\\\"Env\\\":[\\\"k1=v1\\\",\\\"k2=v2\\\",\\\"k3=v3\\\"],\\\"HostConfig\\\":{\\\"PortBindings\\\":{\\\"43/udp\\\":[{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostP\",\"createOptions01\":\"ort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"},{\\\"HostPort\\\":\\\"43\\\"}],\\\"42/tcp\\\":[{\\\"HostPort\\\":\\\"42\\\"}]}}}\"}"; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentProviderTest.cs index f037cd71cb3..0ab00235374 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentProviderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentProviderTest.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Agent.Docker { - using System; - using System.Collections.Generic; - using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Storage; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentTest.cs index b79f72592ff..4ec28c81744 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerEnvironmentTest.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test using System.Linq; using System.Threading; using System.Threading.Tasks; - using global::Docker.DotNet.Models; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; @@ -14,7 +13,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test using Moq; using Newtonsoft.Json; using Xunit; - using RestartPolicy = Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy; [Collection("Docker")] public class DockerEnvironmentTest @@ -38,7 +36,8 @@ public async Task GetRuntimeInfoTest() string minDockerVersion = "20"; string dockerLoggingOptions = "dummy logging options"; - var deploymentConfig = new DeploymentConfig("1.0", + var deploymentConfig = new DeploymentConfig( + "1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig(minDockerVersion, dockerLoggingOptions)), new SystemModules(Option.None(), Option.None()), new Dictionary()); @@ -48,7 +47,7 @@ public async Task GetRuntimeInfoTest() // act IRuntimeInfo reportedRuntimeInfo = await environment.GetRuntimeInfoAsync(); - //. assert + // assert Assert.True(reportedRuntimeInfo is DockerReportedRuntimeInfo); var dockerReported = reportedRuntimeInfo as DockerReportedRuntimeInfo; Assert.Equal(OperatingSystemType, dockerReported.Platform.OperatingSystemType); @@ -99,42 +98,46 @@ public async Task GetModulesTest() string edgeHubHash = Guid.NewGuid().ToString(); string edgeAgentHash = Guid.NewGuid().ToString(); var moduleRuntimeInfoList = new List(); - moduleRuntimeInfoList.Add(new ModuleRuntimeInfo( - "module1", - "docker", - ModuleStatus.Stopped, - "dummy1", - 0, - Option.Some(new DateTime(2017, 10, 10)), - Option.None(), - new DockerReportedConfig("mod1:v1", string.Empty, module1Hash))); - moduleRuntimeInfoList.Add(new ModuleRuntimeInfo( - "module2", - "docker", - ModuleStatus.Failed, - "dummy2", - 5, - Option.Some(new DateTime(2017, 10, 12)), - Option.Some(new DateTime(2017, 10, 14)), - new DockerReportedConfig("mod2:v2", string.Empty, module2Hash))); - moduleRuntimeInfoList.Add(new ModuleRuntimeInfo( - "edgeHub", - "docker", - ModuleStatus.Running, - "", - 0, - Option.Some(new DateTime(2017, 10, 10)), - Option.None(), - new DockerReportedConfig("edgehub:v1", string.Empty, edgeHubHash))); - moduleRuntimeInfoList.Add(new ModuleRuntimeInfo( - "edgeAgent", - "docker", - ModuleStatus.Running, - "", - 0, - Option.Some(new DateTime(2017, 10, 10)), - Option.None(), - new DockerReportedConfig("edgeAgent:v1", string.Empty, edgeAgentHash))); + moduleRuntimeInfoList.Add( + new ModuleRuntimeInfo( + "module1", + "docker", + ModuleStatus.Stopped, + "dummy1", + 0, + Option.Some(new DateTime(2017, 10, 10)), + Option.None(), + new DockerReportedConfig("mod1:v1", string.Empty, module1Hash))); + moduleRuntimeInfoList.Add( + new ModuleRuntimeInfo( + "module2", + "docker", + ModuleStatus.Failed, + "dummy2", + 5, + Option.Some(new DateTime(2017, 10, 12)), + Option.Some(new DateTime(2017, 10, 14)), + new DockerReportedConfig("mod2:v2", string.Empty, module2Hash))); + moduleRuntimeInfoList.Add( + new ModuleRuntimeInfo( + "edgeHub", + "docker", + ModuleStatus.Running, + string.Empty, + 0, + Option.Some(new DateTime(2017, 10, 10)), + Option.None(), + new DockerReportedConfig("edgehub:v1", string.Empty, edgeHubHash))); + moduleRuntimeInfoList.Add( + new ModuleRuntimeInfo( + "edgeAgent", + "docker", + ModuleStatus.Running, + string.Empty, + 0, + Option.Some(new DateTime(2017, 10, 10)), + Option.None(), + new DockerReportedConfig("edgeAgent:v1", string.Empty, edgeAgentHash))); var runtimeInfoProvider = Mock.Of(r => r.GetModules(CancellationToken.None) == Task.FromResult(moduleRuntimeInfoList.AsEnumerable())); var moduleStateStore = new Mock>(); @@ -146,11 +149,12 @@ public async Task GetModulesTest() string minDockerVersion = "20"; string dockerLoggingOptions = "dummy logging options"; - var module1 = new DockerModule("module1", "v1", ModuleStatus.Stopped, Core.RestartPolicy.Always, new DockerConfig("mod1:v1", "{\"Env\":[\"foo=bar\"]}"), new ConfigurationInfo(), null); - var module2 = new DockerModule("module2", "v2", ModuleStatus.Running, Core.RestartPolicy.OnUnhealthy, new DockerConfig("mod2:v2", "{\"Env\":[\"foo2=bar2\"]}"), new ConfigurationInfo(), null); - var edgeHubModule = new EdgeHubDockerModule("docker", ModuleStatus.Running, Core.RestartPolicy.Always, new DockerConfig("edgehub:v1", "{\"Env\":[\"foo3=bar3\"]}"), new ConfigurationInfo(), null); - var edgeAgentModule = new EdgeAgentDockerModule("docker", new DockerConfig("edgeAgent:v1", ""), new ConfigurationInfo(), null); - var deploymentConfig = new DeploymentConfig("1.0", + var module1 = new DockerModule("module1", "v1", ModuleStatus.Stopped, RestartPolicy.Always, new DockerConfig("mod1:v1", "{\"Env\":[\"foo=bar\"]}"), new ConfigurationInfo(), null); + var module2 = new DockerModule("module2", "v2", ModuleStatus.Running, RestartPolicy.OnUnhealthy, new DockerConfig("mod2:v2", "{\"Env\":[\"foo2=bar2\"]}"), new ConfigurationInfo(), null); + var edgeHubModule = new EdgeHubDockerModule("docker", ModuleStatus.Running, RestartPolicy.Always, new DockerConfig("edgehub:v1", "{\"Env\":[\"foo3=bar3\"]}"), new ConfigurationInfo(), null); + var edgeAgentModule = new EdgeAgentDockerModule("docker", new DockerConfig("edgeAgent:v1", string.Empty), new ConfigurationInfo(), null); + var deploymentConfig = new DeploymentConfig( + "1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig(minDockerVersion, dockerLoggingOptions)), new SystemModules(edgeAgentModule, edgeHubModule), new Dictionary { [module1.Name] = module1, [module2.Name] = module2 }); @@ -172,7 +176,7 @@ public async Task GetModulesTest() Assert.Equal("module1", receivedDockerModule1.Name); Assert.Equal("v1", receivedDockerModule1.Version); Assert.Equal(ModuleStatus.Stopped, receivedDockerModule1.DesiredStatus); - Assert.Equal(Core.RestartPolicy.Always, receivedDockerModule1.RestartPolicy); + Assert.Equal(RestartPolicy.Always, receivedDockerModule1.RestartPolicy); Assert.Equal("mod1:v1", receivedDockerModule1.Config.Image); Assert.Equal("{\"Env\":[\"foo=bar\"]}", JsonConvert.SerializeObject(receivedDockerModule1.Config.CreateOptions)); Assert.Equal(ModuleStatus.Stopped, receivedDockerModule1.RuntimeStatus); @@ -189,7 +193,7 @@ public async Task GetModulesTest() Assert.Equal("module2", receivedDockerModule2.Name); Assert.Equal("v2", receivedDockerModule2.Version); Assert.Equal(ModuleStatus.Running, receivedDockerModule2.DesiredStatus); - Assert.Equal(Core.RestartPolicy.OnUnhealthy, receivedDockerModule2.RestartPolicy); + Assert.Equal(RestartPolicy.OnUnhealthy, receivedDockerModule2.RestartPolicy); Assert.Equal("mod2:v2", receivedDockerModule2.Config.Image); Assert.Equal("{\"Env\":[\"foo2=bar2\"]}", JsonConvert.SerializeObject(receivedDockerModule2.Config.CreateOptions)); Assert.Equal(ModuleStatus.Failed, receivedDockerModule2.RuntimeStatus); @@ -204,13 +208,13 @@ public async Task GetModulesTest() var receivedDockerEdgeHub = receivedEdgeHub as EdgeHubDockerRuntimeModule; Assert.NotNull(receivedDockerEdgeHub); Assert.Equal("edgeHub", receivedDockerEdgeHub.Name); - Assert.Equal("", receivedDockerEdgeHub.Version); + Assert.Equal(string.Empty, receivedDockerEdgeHub.Version); Assert.Equal(ModuleStatus.Running, receivedDockerEdgeHub.DesiredStatus); - Assert.Equal(Core.RestartPolicy.Always, receivedDockerEdgeHub.RestartPolicy); + Assert.Equal(RestartPolicy.Always, receivedDockerEdgeHub.RestartPolicy); Assert.Equal("edgehub:v1", receivedDockerEdgeHub.Config.Image); Assert.Equal("{\"Env\":[\"foo3=bar3\"]}", JsonConvert.SerializeObject(receivedDockerEdgeHub.Config.CreateOptions)); Assert.Equal(ModuleStatus.Running, receivedDockerEdgeHub.RuntimeStatus); - Assert.Equal("", receivedDockerEdgeHub.StatusDescription); + Assert.Equal(string.Empty, receivedDockerEdgeHub.StatusDescription); Assert.Equal(0, receivedDockerEdgeHub.ExitCode); Assert.Equal(new DateTime(2017, 10, 10), receivedDockerEdgeHub.LastStartTimeUtc); Assert.Equal(DateTime.MinValue, receivedDockerEdgeHub.LastExitTimeUtc); @@ -221,7 +225,7 @@ public async Task GetModulesTest() var receivedDockerEdgeAgent = receivedEdgeAgent as EdgeAgentDockerRuntimeModule; Assert.NotNull(receivedDockerEdgeAgent); Assert.Equal("edgeAgent", receivedDockerEdgeAgent.Name); - Assert.Equal("", receivedDockerEdgeAgent.Version); + Assert.Equal(string.Empty, receivedDockerEdgeAgent.Version); Assert.Equal(ModuleStatus.Running, receivedDockerEdgeAgent.RuntimeStatus); Assert.Equal("edgeAgent:v1", receivedDockerEdgeAgent.Config.Image); Assert.Equal("{}", JsonConvert.SerializeObject(receivedDockerEdgeAgent.Config.CreateOptions)); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerHelper.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerHelper.cs index 7cc6c912162..7a3567f6663 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerHelper.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerHelper.cs @@ -14,20 +14,13 @@ public static class DockerHelper public static IDockerClient Client { get; } = LazyClient.Value; - static IDockerClient GetDockerClient() - { - // Build the docker host URL. - string dockerHostUrl = ConfigHelper.TestConfig["dockerHostUrl"]; - return new DockerClientConfiguration(new Uri(dockerHostUrl)).CreateClient(); - } - /// /// Pulls specified image and ensures it is downloaded completely /// - /// - /// - /// - /// + /// Docker client + /// Image name + /// Cancellation token + /// Task public static async Task PullImageAsync(this IDockerClient client, string image, CancellationToken token) { string[] imageParts = image.Split(':'); @@ -45,10 +38,10 @@ public static async Task PullImageAsync(this IDockerClient client, string image, /// /// Removes container and deletes image /// - /// - /// - /// - /// + /// Docker client + /// Container name + /// Image name + /// Task public static async Task CleanupContainerAsync(this IDockerClient client, string name, string image) { var removeParams = new ContainerRemoveParameters @@ -70,11 +63,17 @@ public static async Task Safe(Func action) try { await action(); - } - // ReSharper disable once EmptyGeneralCatchClause + } // ReSharper disable once EmptyGeneralCatchClause catch (Exception) { } } + + static IDockerClient GetDockerClient() + { + // Build the docker host URL. + string dockerHostUrl = ConfigHelper.TestConfig["dockerHostUrl"]; + return new DockerClientConfiguration(new Uri(dockerHostUrl)).CreateClient(); + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerLoggingConfigTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerLoggingConfigTest.cs index 90d812758db..a0302c9c50b 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerLoggingConfigTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerLoggingConfigTest.cs @@ -2,9 +2,8 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test { using System; - using System.Collections.Immutable; using System.Collections.Generic; - using Microsoft.Azure.Devices.Edge.Agent.Docker; + using System.Collections.Immutable; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -18,8 +17,8 @@ class DockerLoggingConfigTest static readonly DockerLoggingConfig Config5 = new DockerLoggingConfig("json-file", new Dictionary { { "k1", "v1" }, { "k2", "v2" } }); static readonly DockerLoggingConfig Config6 = new DockerLoggingConfig("json-file", LoggingConfig1); static readonly DockerLoggingConfig Config7 = new DockerLoggingConfig("json-file", LoggingConfig1); - static readonly DockerLoggingConfig Config8 = new DockerLoggingConfig("json-file", new Dictionary { { "k1", "v1" }}); - static readonly DockerLoggingConfig Config9 = new DockerLoggingConfig("json-file", new Dictionary { { "k1", "v2" }}); + static readonly DockerLoggingConfig Config8 = new DockerLoggingConfig("json-file", new Dictionary { { "k1", "v1" } }); + static readonly DockerLoggingConfig Config9 = new DockerLoggingConfig("json-file", new Dictionary { { "k1", "v2" } }); [Fact] [Unit] @@ -34,7 +33,6 @@ public void TestConstruction() [Unit] public void TestEquals() { - Assert.False(Config1.Equals(null)); Assert.True(Config1.Equals((object)Config1)); Assert.False(Config1.Equals(Config3)); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerModuleTest.cs index 7ba1c3d4970..c8ab697a8f0 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerModuleTest.cs @@ -4,18 +4,18 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Linq; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Core.Serde; - using Microsoft.Azure.Devices.Edge.Agent.Docker; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Newtonsoft.Json; - using Xunit; using Newtonsoft.Json.Linq; - using System.Linq; + using Xunit; [ExcludeFromCodeCoverage] public class DockerModuleTest { + const string SerializedModule = @"{""version"":""version1"",""type"":""docker"",""status"":""running"",""restartPolicy"":""on-unhealthy"",""settings"":{""image"":""image1:42"", ""createOptions"": {""HostConfig"": {""PortBindings"": {""43/udp"": [{""HostPort"": ""43""}], ""42/tcp"": [{""HostPort"": ""42""}]}}}},""configuration"":{""id"":""1""},""env"":{""Env1"": {""value"":""Val1""}}}"; static readonly ConfigurationInfo DefaultConfigurationInfo = null; static readonly DockerConfig Config1 = new DockerConfig("image1:42", @"{""HostConfig"": {""PortBindings"": {""43/udp"": [{""HostPort"": ""43""}], ""42/tcp"": [{""HostPort"": ""42""}]}}}"); static readonly DockerConfig Config2 = new DockerConfig("image2:42", @"{""HostConfig"": {""PortBindings"": {""43/udp"": [{""HostPort"": ""43""}], ""42/tcp"": [{""HostPort"": ""42""}]}}}"); @@ -31,10 +31,8 @@ public class DockerModuleTest static readonly IModule Module10 = new DockerModule("mod1", "version1", ModuleStatus.Running, RestartPolicy.OnUnhealthy, Config1, DefaultConfigurationInfo, new Dictionary { ["Env1"] = new EnvVal("Val2") }); static readonly IModule ModuleWithConfig = new DockerModule("mod1", "version1", ModuleStatus.Running, RestartPolicy.Always, Config1, new ConfigurationInfo("c1"), DefaultEnvVals); static readonly DockerModule ValidJsonModule = new DockerModule("", "", ModuleStatus.Running, RestartPolicy.OnUnhealthy, Config1, DefaultConfigurationInfo, DefaultEnvVals); - - const string SerializedModule = @"{""version"":""version1"",""type"":""docker"",""status"":""running"",""restartPolicy"":""on-unhealthy"",""settings"":{""image"":""image1:42"", ""createOptions"": {""HostConfig"": {""PortBindings"": {""43/udp"": [{""HostPort"": ""43""}], ""42/tcp"": [{""HostPort"": ""42""}]}}}},""configuration"":{""id"":""1""},""env"":{""Env1"": {""value"":""Val1""}}}"; - - static readonly JObject TestJsonInputs = JsonConvert.DeserializeObject(@" + static readonly JObject TestJsonInputs = JsonConvert.DeserializeObject( + @" { ""invalidEnvJson"":[ { @@ -334,27 +332,6 @@ public void TestEquality() Assert.NotEqual(Module1.GetHashCode(), Module9.GetHashCode()); } - static IEnumerable GetJsonTestCases(string subset) - { - var val = (JArray)TestJsonInputs.GetValue(subset); - return val.Children().Select(token => token.ToString()); - } - - static IEnumerable GetValidJsonInputs() - { - return GetJsonTestCases("validJson").Select(s => new object[] { s }); - } - - static IEnumerable GetExceptionJsonInputs() - { - return GetJsonTestCases("throwsException").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidEnvJsonInputs() - { - return GetJsonTestCases("invalidEnvJson").Select(s => new object[] { s }); - } - [Theory] [Unit] [MemberData(nameof(GetValidJsonInputs))] @@ -436,5 +413,26 @@ public void TestDeserializeInvalidEnvJson(string inputJson) Assert.Throws( () => ModuleSerde.Instance.Deserialize(inputJson)); } + + static IEnumerable GetJsonTestCases(string subset) + { + var val = (JArray)TestJsonInputs.GetValue(subset); + return val.Children().Select(token => token.ToString()); + } + + static IEnumerable GetValidJsonInputs() + { + return GetJsonTestCases("validJson").Select(s => new object[] { s }); + } + + static IEnumerable GetExceptionJsonInputs() + { + return GetJsonTestCases("throwsException").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidEnvJsonInputs() + { + return GetJsonTestCases("invalidEnvJson").Select(s => new object[] { s }); + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerRuntimeModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerRuntimeModuleTest.cs index 2619891ffc3..4f764ed94a2 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerRuntimeModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerRuntimeModuleTest.cs @@ -8,16 +8,18 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test using System.Linq; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Core.Serde; - using Microsoft.Azure.Devices.Edge.Agent.Docker; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Newtonsoft.Json; - using Xunit; using Newtonsoft.Json.Linq; + using Xunit; [ExcludeFromCodeCoverage] [Unit] public class DockerRuntimeModuleTest { + const string SerializedModule1 = @"{""version"":""version1"",""type"":""docker"",""status"":""running"",""restartPolicy"":""on-unhealthy"",""exitcode"":0,""restartcount"":0,""lastrestarttimeutc"":""0001-01-01T00:00:00Z"",""runtimestatus"":""running"",""settings"":{""image"":""image1:42"",""createOptions"":{""HostConfig"":{""PortBinding"":{""42/tcp"":[{""HostPort"":""42""}],""43/udp"":[{""HostPort"":""43""}]}}}},""configuration"":{""id"":""1""}}"; + const string SerializedModule2 = @"{""version"":""version1"",""type"":""docker"",""status"":""running"",""restartPolicy"":""on-unhealthy"",""exitcode"":0,""statusdescription"":""Running 1 minute"",""laststarttimeutc"":""2017-08-04T17:52:13.0419502Z"",""lastexittimeutc"":""0001-01-01T00:00:00Z"",""restartcount"":0,""lastrestarttimeutc"":""0001-01-01T00:00:00Z"",""runtimestatus"":""running"",""settings"":{""image"":""image1:42"",""createOptions"":{""HostConfig"":{""PortBinding"":{""42/tcp"":[{""HostPort"":""42""}],""43/udp"":[{""HostPort"":""43""}]}}}},""configuration"":{""id"":""1""}}"; + static readonly ConfigurationInfo DefaultConfigurationInfo = null; static readonly IDictionary EnvVars = new Dictionary(); static readonly DockerConfig Config1 = new DockerReportedConfig("image1:42", @"{""HostConfig"": {""PortBinding"": {""42/tcp"": [{""HostPort"": ""42""}], ""43/udp"": [{""HostPort"": ""43""}]}}}", "foo"); @@ -41,10 +43,8 @@ public class DockerRuntimeModuleTest static readonly DockerConfig ValidConfig = new DockerReportedConfig("image1:42", (string)null, "sha256:75"); static readonly DockerRuntimeModule ValidJsonModule = new DockerRuntimeModule("", "", ModuleStatus.Running, RestartPolicy.OnFailure, ValidConfig, 0, "", DateTime.Parse("2017-08-04T17:52:13.0419502Z", null, DateTimeStyles.RoundtripKind), DateTime.Parse("2017-08-05T17:52:13.0419502Z", null, DateTimeStyles.RoundtripKind), 1, DateTime.Parse("2017-08-06T17:52:13.0419502Z", null, DateTimeStyles.RoundtripKind), ModuleStatus.Running, DefaultConfigurationInfo, EnvVars); - const string SerializedModule1 = @"{""version"":""version1"",""type"":""docker"",""status"":""running"",""restartPolicy"":""on-unhealthy"",""exitcode"":0,""restartcount"":0,""lastrestarttimeutc"":""0001-01-01T00:00:00Z"",""runtimestatus"":""running"",""settings"":{""image"":""image1:42"",""createOptions"":{""HostConfig"":{""PortBinding"":{""42/tcp"":[{""HostPort"":""42""}],""43/udp"":[{""HostPort"":""43""}]}}}},""configuration"":{""id"":""1""}}"; - const string SerializedModule2 = @"{""version"":""version1"",""type"":""docker"",""status"":""running"",""restartPolicy"":""on-unhealthy"",""exitcode"":0,""statusdescription"":""Running 1 minute"",""laststarttimeutc"":""2017-08-04T17:52:13.0419502Z"",""lastexittimeutc"":""0001-01-01T00:00:00Z"",""restartcount"":0,""lastrestarttimeutc"":""0001-01-01T00:00:00Z"",""runtimestatus"":""running"",""settings"":{""image"":""image1:42"",""createOptions"":{""HostConfig"":{""PortBinding"":{""42/tcp"":[{""HostPort"":""42""}],""43/udp"":[{""HostPort"":""43""}]}}}},""configuration"":{""id"":""1""}}"; - - static readonly JObject TestJsonInputs = JsonConvert.DeserializeObject(@" + static readonly JObject TestJsonInputs = JsonConvert.DeserializeObject( + @" { ""invalidExitCodeJson"":[ { @@ -465,51 +465,6 @@ public class DockerRuntimeModuleTest ], } "); - static IEnumerable GetJsonTestCases(string subset) - { - var val = (JArray)TestJsonInputs.GetValue(subset); - return val.Children().Select(token => token.ToString()); - } - - static IEnumerable GetValidJsonInputs() - { - return GetJsonTestCases("validJson").Select(s => new object[] { s }); - } - - static IEnumerable GetValidStatusInputs() - { - return GetJsonTestCases("validStatus").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidExitCodes() - { - return GetJsonTestCases("invalidExitCodeJson").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidStatusDescription() - { - return GetJsonTestCases("invalidStatusDescription").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidLastStartTimes() - { - return GetJsonTestCases("invalidLastStartTime").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidLastExitTimes() - { - return GetJsonTestCases("invalidLastExitTime").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidRestartCounts() - { - return GetJsonTestCases("invalidRestartCount").Select(s => new object[] { s }); - } - - static IEnumerable GetInvalidLastRestartTimes() - { - return GetJsonTestCases("invalidLastRestartTime").Select(s => new object[] { s }); - } [Fact] public void TestConstructor() @@ -694,5 +649,51 @@ public void TestWithRuntimeStatus() Assert.Equal(m2.Type, newM2.Type); Assert.Equal(m2.Version, newM2.Version); } + + static IEnumerable GetJsonTestCases(string subset) + { + var val = (JArray)TestJsonInputs.GetValue(subset); + return val.Children().Select(token => token.ToString()); + } + + static IEnumerable GetValidJsonInputs() + { + return GetJsonTestCases("validJson").Select(s => new object[] { s }); + } + + static IEnumerable GetValidStatusInputs() + { + return GetJsonTestCases("validStatus").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidExitCodes() + { + return GetJsonTestCases("invalidExitCodeJson").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidStatusDescription() + { + return GetJsonTestCases("invalidStatusDescription").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidLastStartTimes() + { + return GetJsonTestCases("invalidLastStartTime").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidLastExitTimes() + { + return GetJsonTestCases("invalidLastExitTime").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidRestartCounts() + { + return GetJsonTestCases("invalidRestartCount").Select(s => new object[] { s }); + } + + static IEnumerable GetInvalidLastRestartTimes() + { + return GetJsonTestCases("invalidLastRestartTime").Select(s => new object[] { s }); + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerUtilTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerUtilTest.cs index 6d5a304ebce..7c4b79f0424 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerUtilTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/DockerUtilTest.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test using System; using System.Collections.Generic; using global::Docker.DotNet.Models; - using Microsoft.Azure.Devices.Edge.Agent.Docker; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -49,7 +48,7 @@ public void CanParseHostnameFromImage(string image, string expectedHostname) public void ThrowsWhenImageArgumentIsNull() { var authConfigs = new List(); - authConfigs.Add(new AuthConfig()); //Adding something so we don't have an empty list. reSharper warns if List is empty. + authConfigs.Add(new AuthConfig()); // Adding something so we don't have an empty list. reSharper warns if List is empty. Assert.Throws(() => authConfigs.FirstAuthConfig(null)); } @@ -62,7 +61,7 @@ public void ReturnsNullWhenAuthConfigListArgumentIsNull() [Fact] public void ReturnsNullWhenListIsEmpty() { - // The test needs an empty list. + // The test needs an empty list. // ReSharper disable once CollectionNeverUpdated.Local var authConfigs = new List(); Option found = authConfigs.FirstAuthConfig("hostname/repo/imagename"); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerModuleTest.cs index 6e8b386347b..ebfb8b39698 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerModuleTest.cs @@ -19,22 +19,22 @@ public static IEnumerable GetEaModules() yield return new object[] { - new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), new ConfigurationInfo("c1"), new Dictionary{ ["Env1"] = new EnvVal("EnvVal1")}, "version1"), + new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), new ConfigurationInfo("c1"), new Dictionary { ["Env1"] = new EnvVal("EnvVal1") }, "version1"), new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), null, null), true - }; + }; yield return new object[] { - new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), new ConfigurationInfo("c1"), new Dictionary{ ["Env1"] = new EnvVal("EnvVal1")}, "version1"), - new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), new ConfigurationInfo("c2"), new Dictionary{ ["Env2"] = new EnvVal("EnvVal2")}, "version2"), + new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), new ConfigurationInfo("c1"), new Dictionary { ["Env1"] = new EnvVal("EnvVal1") }, "version1"), + new EdgeAgentDockerModule("docker", new DockerConfig("Foo"), new ConfigurationInfo("c2"), new Dictionary { ["Env2"] = new EnvVal("EnvVal2") }, "version2"), true }; yield return new object[] { - new EdgeAgentDockerModule("docker", new DockerConfig("Foo", "{}"), new ConfigurationInfo("c1"), new Dictionary{ ["Env1"] = new EnvVal("EnvVal1")}, "version1"), - new EdgeAgentDockerModule("docker", new DockerConfig("Foo", "{\"HostConfig\":{\"PortBindings\":{\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"), new ConfigurationInfo("c2"), new Dictionary{ ["Env2"] = new EnvVal("EnvVal2")}, "version2"), + new EdgeAgentDockerModule("docker", new DockerConfig("Foo", "{}"), new ConfigurationInfo("c1"), new Dictionary { ["Env1"] = new EnvVal("EnvVal1") }, "version1"), + new EdgeAgentDockerModule("docker", new DockerConfig("Foo", "{\"HostConfig\":{\"PortBindings\":{\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"), new ConfigurationInfo("c2"), new Dictionary { ["Env2"] = new EnvVal("EnvVal2") }, "version2"), true }; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerRuntimeModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerRuntimeModuleTest.cs index a367c420917..cb79c81011d 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerRuntimeModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeAgentDockerRuntimeModuleTest.cs @@ -23,33 +23,33 @@ public void TestJsonSerialize() new DockerReportedConfig("booyah", string.Empty, "someSha"), ModuleStatus.Running, 0, - "", + string.Empty, lastStartTimeUtc, lastExitTimeUtc, null, - new Dictionary() - ); + new Dictionary()); // Act JToken json = JToken.Parse(JsonConvert.SerializeObject(module)); // Assert - JToken expected = JToken.FromObject(new - { - runtimeStatus = "running", - exitCode = 0, - lastStartTimeUtc = lastStartTimeUtc, - lastExitTimeUtc = lastExitTimeUtc, - statusDescription = "", - type = "docker", - settings = new + JToken expected = JToken.FromObject( + new { - image = "booyah", - imageHash = "someSha", - createOptions = "{}" - }, - env = new { } - }); + runtimeStatus = "running", + exitCode = 0, + lastStartTimeUtc = lastStartTimeUtc, + lastExitTimeUtc = lastExitTimeUtc, + statusDescription = string.Empty, + type = "docker", + settings = new + { + image = "booyah", + imageHash = "someSha", + createOptions = "{}" + }, + env = new { } + }); Assert.True(JToken.DeepEquals(expected, json)); } @@ -60,20 +60,22 @@ public void TestJsonDeserialize() { // Arrange DateTime lastStartTimeUtc = DateTime.Parse( - "2017-11-13T23:44:35.127381Z", null, DateTimeStyles.RoundtripKind - ); - string json = JsonConvert.SerializeObject(new - { - type = "docker", - runtimeStatus = "running", - settings = new + "2017-11-13T23:44:35.127381Z", + null, + DateTimeStyles.RoundtripKind); + string json = JsonConvert.SerializeObject( + new { - image = "someImage", - createOptions = "{}", - imageHash = "someSha" - }, - lastStartTimeUtc = lastStartTimeUtc - }); + type = "docker", + runtimeStatus = "running", + settings = new + { + image = "someImage", + createOptions = "{}", + imageHash = "someSha" + }, + lastStartTimeUtc = lastStartTimeUtc + }); // Act var edgeAgent = JsonConvert.DeserializeObject(json); @@ -83,7 +85,7 @@ public void TestJsonDeserialize() Assert.Equal(ModuleStatus.Running, edgeAgent.RuntimeStatus); Assert.Equal("someImage", edgeAgent.Config.Image); // TODO - Change Config for Runtime to DockerReportedConfig. - //Assert.Equal("someSha", (edgeAgent.Config as DockerReportedConfig)?.ImageHash); + // Assert.Equal("someSha", (edgeAgent.Config as DockerReportedConfig)?.ImageHash); Assert.Equal(lastStartTimeUtc, edgeAgent.LastStartTimeUtc); } @@ -92,21 +94,22 @@ public void TestJsonDeserialize() public void TestJsonDeserialize2() { // Arrange - string json = JsonConvert.SerializeObject(new - { - type = "docker", - runtimeStatus = "running", - settings = new - { - image = "someImage", - createOptions = "{}", - imageHash = "someSha" - }, - configuration = new + string json = JsonConvert.SerializeObject( + new { - id = "bing" - } - }); + type = "docker", + runtimeStatus = "running", + settings = new + { + image = "someImage", + createOptions = "{}", + imageHash = "someSha" + }, + configuration = new + { + id = "bing" + } + }); // Act var edgeAgent = JsonConvert.DeserializeObject(json); @@ -130,12 +133,11 @@ public void TestWithRuntimeStatus() new DockerReportedConfig("booyah", string.Empty, "someSha"), ModuleStatus.Running, 0, - "", + string.Empty, lastStartTimeUtc, lastExitTimeUtc, new ConfigurationInfo("bing"), - new Dictionary() - ); + new Dictionary()); var updatedModule1 = (EdgeAgentDockerRuntimeModule)module.WithRuntimeStatus(ModuleStatus.Running); var updatedModule2 = (EdgeAgentDockerRuntimeModule)module.WithRuntimeStatus(ModuleStatus.Unknown); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeHubDockerRuntimeModuleTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeHubDockerRuntimeModuleTest.cs index 1073d801bc6..5cec89d02e1 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeHubDockerRuntimeModuleTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/EdgeHubDockerRuntimeModuleTest.cs @@ -17,18 +17,25 @@ public void TestJsonSerialize() { // Arrange var module = new EdgeHubDockerRuntimeModule( - ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig("edg0eHubImage"), 0, string.Empty, - DateTime.MinValue, DateTime.MinValue, 0, - DateTime.MinValue, ModuleStatus.Running, - new ConfigurationInfo("1"), new Dictionary() - ); + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig("edg0eHubImage"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + new ConfigurationInfo("1"), + new Dictionary()); // Act JToken json = JToken.Parse(JsonConvert.SerializeObject(module)); // Assert - JToken expected = JToken.Parse(@" + JToken expected = JToken.Parse( + @" { ""status"": ""running"", ""restartPolicy"": ""always"", @@ -79,12 +86,18 @@ public void TestJsonDeerialize() // Assert var expected = new EdgeHubDockerRuntimeModule( - ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig("edg0eHubImage"), 0, string.Empty, - DateTime.MinValue, DateTime.MinValue, 0, - DateTime.MinValue, ModuleStatus.Running, - null, new Dictionary() - ); + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig("edg0eHubImage"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + null, + new Dictionary()); Assert.Equal(expected, actual); } @@ -96,15 +109,26 @@ public void EqualsTest() // Arrange string image = "repo/microsoft/azureiotedge-hub:002"; var edgeHubDockerModule = new EdgeHubDockerModule( - "docker", ModuleStatus.Running, RestartPolicy.Always, new DockerConfig(image), new ConfigurationInfo("1"), new Dictionary()); + "docker", + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig(image), + new ConfigurationInfo("1"), + new Dictionary()); var edgeHubDockerRuntimeModule = new EdgeHubDockerRuntimeModule( - ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig(image), 0, string.Empty, - DateTime.MinValue, DateTime.MinValue, 0, - DateTime.MinValue, ModuleStatus.Running, - new ConfigurationInfo("1"), new Dictionary() - ); + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig(image), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + new ConfigurationInfo("1"), + new Dictionary()); // Act bool equal = edgeHubDockerModule.Equals(edgeHubDockerRuntimeModule); @@ -118,12 +142,18 @@ public void EqualsTest() public void TestWithRuntimeStatus() { var module = new EdgeHubDockerRuntimeModule( - ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig("edg0eHubImage"), 0, string.Empty, - DateTime.MinValue, DateTime.MinValue, 0, - DateTime.MinValue, ModuleStatus.Running, - new ConfigurationInfo("1"), new Dictionary() - ); + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig("edg0eHubImage"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + new ConfigurationInfo("1"), + new Dictionary()); var updatedModule1 = (EdgeHubDockerRuntimeModule)module.WithRuntimeStatus(ModuleStatus.Running); var updatedModule2 = (EdgeHubDockerRuntimeModule)module.WithRuntimeStatus(ModuleStatus.Unknown); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test.csproj index 8f59d79bf70..3707d08d211 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test.csproj +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test.csproj @@ -39,4 +39,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/RuntimeInfoProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/RuntimeInfoProviderTest.cs index ef0de71b819..365bd0e06f0 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/RuntimeInfoProviderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/RuntimeInfoProviderTest.cs @@ -20,12 +20,12 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test public class RuntimeInfoProviderTest { + const string OperatingSystemType = "linux"; + const string Architecture = "x86_x64"; static readonly TimeSpan Timeout = TimeSpan.FromSeconds(300); static readonly IDockerClient Client = DockerHelper.Client; static readonly IEntityStore RestartStateStore = new Mock>().Object; static readonly IRestartPolicyManager RestartManager = new Mock().Object; - const string OperatingSystemType = "linux"; - const string Architecture = "x86_x64"; [Integration] [Fact] @@ -77,16 +77,17 @@ public async Task TestFilters() var loggingConfig = new DockerLoggingConfig("json-file"); var config = new DockerConfig(Image); - var module = new DockerModule(Name, "1.0", ModuleStatus.Running, Core.RestartPolicy.OnUnhealthy, config, null, null); + var module = new DockerModule(Name, "1.0", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, config, null, null); - IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary - { - { "EdgeDeviceConnectionString", fakeConnectionString } - }).Build(); + IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( + new Dictionary + { + { "EdgeDeviceConnectionString", fakeConnectionString } + }).Build(); var deploymentConfigModules = new Dictionary { [Name] = module }; var systemModules = new SystemModules(null, null); - var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", "")), systemModules, deploymentConfigModules)); + var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", string.Empty)), systemModules, deploymentConfigModules)); var configSource = new Mock(); configSource.Setup(cs => cs.Configuration).Returns(configRoot); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()).ReturnsAsync(deploymentConfigInfo); @@ -142,16 +143,17 @@ public async Task TestEnvVars() string createOptions = @"{""Env"": [ ""k1=v1"", ""k2=v2""]}"; var config = new DockerConfig(Image, createOptions); var loggingConfig = new DockerLoggingConfig("json-file"); - var module = new DockerModule(Name, "1.0", ModuleStatus.Running, Core.RestartPolicy.OnUnhealthy, config, null, null); + var module = new DockerModule(Name, "1.0", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, config, null, null); - IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary - { - { "EdgeDeviceConnectionString", fakeConnectionString } - }).Build(); + IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( + new Dictionary + { + { "EdgeDeviceConnectionString", fakeConnectionString } + }).Build(); var deploymentConfigModules = new Dictionary { [Name] = module }; var systemModules = new SystemModules(null, null); - var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", "")), systemModules, deploymentConfigModules)); + var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", string.Empty)), systemModules, deploymentConfigModules)); var configSource = new Mock(); configSource.Setup(cs => cs.Configuration).Returns(configRoot); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()).ReturnsAsync(deploymentConfigInfo); @@ -211,9 +213,10 @@ public void InspectResponseToModuleTest() Architecture = Architecture }; - var dockerClient = Mock.Of(dc => - dc.Containers == Mock.Of(co => co.InspectContainerAsync(id, default(CancellationToken)) == Task.FromResult(inspectContainerResponse)) && - dc.System == Mock.Of(so => so.GetSystemInfoAsync(default(CancellationToken)) == Task.FromResult(systemInfoResponse))); + var dockerClient = Mock.Of( + dc => + dc.Containers == Mock.Of(co => co.InspectContainerAsync(id, default(CancellationToken)) == Task.FromResult(inspectContainerResponse)) && + dc.System == Mock.Of(so => so.GetSystemInfoAsync(default(CancellationToken)) == Task.FromResult(systemInfoResponse))); // Act ModuleRuntimeInfo module = RuntimeInfoProvider.InspectResponseToModule(inspectContainerResponse); @@ -222,7 +225,7 @@ public void InspectResponseToModuleTest() Assert.NotNull(module); var dockerModule = module as ModuleRuntimeInfo; Assert.NotNull(dockerModule); - Assert.Equal("", dockerModule.Config.Image); + Assert.Equal(string.Empty, dockerModule.Config.Image); Assert.Equal("sensor", dockerModule.Name); Assert.Equal(0, dockerModule.ExitCode); @@ -242,8 +245,9 @@ public async Task GetSystemInfoTest() Architecture = Architecture }; - var dockerClient = Mock.Of(dc => - dc.System == Mock.Of(so => so.GetSystemInfoAsync(default(CancellationToken)) == Task.FromResult(systemInfoResponse))); + var dockerClient = Mock.Of( + dc => + dc.System == Mock.Of(so => so.GetSystemInfoAsync(default(CancellationToken)) == Task.FromResult(systemInfoResponse))); var inputRuntimeInfo = new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.13", string.Empty)); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/CreateCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/CreateCommandTest.cs index 1777b005936..08aeeb9c29d 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/CreateCommandTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/CreateCommandTest.cs @@ -53,7 +53,7 @@ public async Task SmokeTest() // Logging options will be derived from these default logging options var loggingConfig = new DockerLoggingConfig("json-file", dockerLoggingOptions); var config = new DockerConfig(Image, @"{""Env"": [""k1=v1"", ""k2=v2""], ""HostConfig"": {""PortBindings"": {""8080/tcp"": [{""HostPort"": ""80""}]}}}"); - var module = new DockerModule(Name, "1.0", ModuleStatus.Running, Core.RestartPolicy.OnUnhealthy, config, null, EnvVars); + var module = new DockerModule(Name, "1.0", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, config, null, EnvVars); IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary @@ -64,7 +64,7 @@ public async Task SmokeTest() var modules = new Dictionary { [Name] = module }; var systemModules = new SystemModules(null, null); - var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", "")), systemModules, modules)); + var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", string.Empty)), systemModules, modules)); var configSource = new Mock(); configSource.Setup(cs => cs.Configuration).Returns(configRoot); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()).ReturnsAsync(deploymentConfigInfo); @@ -124,7 +124,7 @@ public async Task TestUdpModuleConfig() var loggingConfig = new DockerLoggingConfig("json-file"); var config = new DockerConfig(Image, @"{""HostConfig"": {""PortBindings"": {""42/udp"": [{""HostPort"": ""42""}]}}}"); - var module = new DockerModule(Name, "1.0", ModuleStatus.Running, Core.RestartPolicy.OnUnhealthy, config, null, EnvVars); + var module = new DockerModule(Name, "1.0", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, config, null, EnvVars); IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary @@ -201,7 +201,7 @@ public async Task EdgeHubLaunch() // Logging options will be derived from module options. var config = new DockerConfig(Image, @"{""Env"": [""k1=v1"", ""k2=v2""], ""HostConfig"": {""LogConfig"": {""Type"":""none""}, ""PortBindings"": {""8080/tcp"": [{""HostPort"": ""80""}],""443/tcp"": [{""HostPort"": ""11443""}]}}}"); var configurationInfo = new ConfigurationInfo(); - var module = new EdgeHubDockerModule("docker", ModuleStatus.Running, Core.RestartPolicy.Always, config, configurationInfo, EnvVars); + var module = new EdgeHubDockerModule("docker", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, config, configurationInfo, EnvVars); IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary @@ -214,7 +214,7 @@ public async Task EdgeHubLaunch() }).Build(); var modules = new Dictionary { [Name] = module }; var systemModules = new SystemModules(null, null); - var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", "")), systemModules, modules)); + var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", string.Empty)), systemModules, modules)); var configSource = new Mock(); configSource.Setup(cs => cs.Configuration).Returns(configRoot); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()).ReturnsAsync(deploymentConfigInfo); @@ -247,7 +247,7 @@ public async Task EdgeHubLaunch() Assert.Equal("testdevice", container.NetworkSettings.Networks.GetOrElse("testnetwork", new EndpointSettings()).Aliases.FirstOrDefault()); Assert.Equal("testdevice", container.NetworkSettings.Networks.GetOrElse("testnetwork", new EndpointSettings()).Aliases.FirstOrDefault()); - //environment variables + // environment variables IDictionary envMap = container.Config.Env.ToDictionary('='); Assert.Equal("v1", envMap["k1"]); Assert.Equal("v2", envMap["k2"]); @@ -288,7 +288,7 @@ public async Task EdgeHubLaunchWithBadLogOptions() var loggingConfig = new DockerLoggingConfig("json-file", dockerLoggingOptions); var config = new DockerConfig(Image, @"{""Env"": [""k1=v1"", ""k2=v2""]}"); var configurationInfo = new ConfigurationInfo("43"); - var module = new EdgeHubDockerModule("docker", ModuleStatus.Running, Core.RestartPolicy.Always, config, configurationInfo, EnvVars); + var module = new EdgeHubDockerModule("docker", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, config, configurationInfo, EnvVars); IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary @@ -341,24 +341,6 @@ public async Task EdgeHubLaunchWithBadLogOptions() } } - IEdgeAgentModule CreateMockEdgeAgentModule() => new TestAgentModule( - Constants.EdgeAgentModuleName, - "docker", - new TestConfig("EdgeAgentImage"), - new ConfigurationInfo(), - EnvVars - ); - - IEdgeHubModule CreateMockEdgeHubModule() => new TestHubModule( - Constants.EdgeHubModuleName, - "docker", - ModuleStatus.Running, - new TestConfig("EdgeAgentImage"), - Core.RestartPolicy.Always, - new ConfigurationInfo(), - EnvVars - ); - [Fact] [Unit] public async Task TestMountEdgeHubVolume() @@ -387,24 +369,21 @@ public async Task TestMountEdgeHubVolume() var systemModules = new SystemModules( this.CreateMockEdgeAgentModule(), - this.CreateMockEdgeHubModule() - ); + this.CreateMockEdgeHubModule()); IConfigurationRoot configuration = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary { { Constants.EdgeHubVolumeNameKey, VolumeName }, { Constants.EdgeHubVolumePathKey, VolumePath } - } - ).Build(); + }).Build(); var configSource = new Mock(); var deploymentConfig = new DeploymentConfig( "1.0", runtimeInfo.Object, systemModules, - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deploymentConfigInfo = new DeploymentConfigInfo(10, deploymentConfig); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()) .ReturnsAsync(deploymentConfigInfo); @@ -418,11 +397,10 @@ public async Task TestMountEdgeHubVolume() "mod1", "1.0", ModuleStatus.Running, - Core.RestartPolicy.OnUnhealthy, + global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, new DockerConfig("image1"), new ConfigurationInfo("1234"), - EnvVars - ), + EnvVars), moduleIdentity.Object, new DockerLoggingConfig("json"), configSource.Object, @@ -462,24 +440,21 @@ public async Task TestMountModuleVolume() var systemModules = new SystemModules( this.CreateMockEdgeAgentModule(), - this.CreateMockEdgeHubModule() - ); + this.CreateMockEdgeHubModule()); IConfigurationRoot configuration = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary { { Constants.EdgeModuleVolumeNameKey, VolumeName }, { Constants.EdgeModuleVolumePathKey, VolumePath } - } - ).Build(); + }).Build(); var configSource = new Mock(); var deploymentConfig = new DeploymentConfig( "1.0", runtimeInfo.Object, systemModules, - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deploymentConfigInfo = new DeploymentConfigInfo(10, deploymentConfig); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()) .ReturnsAsync(deploymentConfigInfo); @@ -493,11 +468,10 @@ public async Task TestMountModuleVolume() "mod1", "1.0", ModuleStatus.Running, - Core.RestartPolicy.OnUnhealthy, + global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, new DockerConfig("image1"), new ConfigurationInfo("1234"), - EnvVars - ), + EnvVars), moduleIdentity.Object, new DockerLoggingConfig("json"), configSource.Object, @@ -698,7 +672,7 @@ public async Task UpstreamProtocolTest() // Logging options will be derived from these default logging options var loggingConfig = new DockerLoggingConfig("json-file", dockerLoggingOptions); var config = new DockerConfig(Image, @"{""Env"": [""k1=v1"", ""k2=v2""], ""HostConfig"": {""PortBindings"": {""8080/tcp"": [{""HostPort"": ""80""}]}}}"); - var module = new DockerModule(Name, "1.0", ModuleStatus.Running, Core.RestartPolicy.OnUnhealthy, config, null, EnvVars); + var module = new DockerModule(Name, "1.0", ModuleStatus.Running, global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.OnUnhealthy, config, null, EnvVars); IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary @@ -710,7 +684,7 @@ public async Task UpstreamProtocolTest() var modules = new Dictionary { [Name] = module }; var systemModules = new SystemModules(null, null); - var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", "")), systemModules, modules)); + var deploymentConfigInfo = new DeploymentConfigInfo(1, new DeploymentConfig("1.0", new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.25", string.Empty)), systemModules, modules)); var configSource = new Mock(); configSource.Setup(cs => cs.Configuration).Returns(configRoot); configSource.Setup(cs => cs.GetDeploymentConfigInfoAsync()).ReturnsAsync(deploymentConfigInfo); @@ -751,5 +725,21 @@ public async Task UpstreamProtocolTest() await DockerHelper.Client.CleanupContainerAsync(Name, Image); } } + + IEdgeAgentModule CreateMockEdgeAgentModule() => new TestAgentModule( + Constants.EdgeAgentModuleName, + "docker", + new TestConfig("EdgeAgentImage"), + new ConfigurationInfo(), + EnvVars); + + IEdgeHubModule CreateMockEdgeHubModule() => new TestHubModule( + Constants.EdgeHubModuleName, + "docker", + ModuleStatus.Running, + new TestConfig("EdgeAgentImage"), + global::Microsoft.Azure.Devices.Edge.Agent.Core.RestartPolicy.Always, + new ConfigurationInfo(), + EnvVars); } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/PullCommandTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/PullCommandTest.cs index 221f4dc199d..b4cd06b6743 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/PullCommandTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Docker.Test/commands/PullCommandTest.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Docker.Test.Commands using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; + using System.Net; using System.Threading; using System.Threading.Tasks; using global::Docker.DotNet; @@ -22,39 +23,29 @@ public class PullCommandTest { static readonly Option NoAuth = Option.None(); - static IEnumerable CreateTestData() - { - (string testFullImage, string image, string tag)[] testInputRecords = { - ("localhost:5000/edge-hub:latest", "localhost:5000/edge-hub", "latest"), - ("edgebuilds.azurecr.io/azedge-edge-agent-x64:latest","edgebuilds.azurecr.io/azedge-edge-agent-x64","latest"), - ("mongo:3.4.4", "mongo", "3.4.4"), - ("edgebuilds.azurecr.io/azedge-simulated-temperature-sensor-x64", "edgebuilds.azurecr.io/azedge-simulated-temperature-sensor-x64", string.Empty) - }; - return testInputRecords.Select(r => new object[] { r.testFullImage, r.image, r.tag }).AsEnumerable(); - } - [Theory] [Unit] [MemberData(nameof(CreateTestData))] public async Task PullValidImages(string testFullImage, string image, string tag) { // Arrange - - string testImage = string.Empty; string testTag = string.Empty; var auth = new AuthConfig(); var client = new Mock(); var images = new Mock(); - images.Setup(i => i.CreateImageAsync(It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Callback, CancellationToken>((icp, a, p, t) => - { - testImage = icp.FromImage; - testTag = icp.Tag; - }) + images.Setup( + i => i.CreateImageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Callback, CancellationToken>( + (icp, a, p, t) => + { + testImage = icp.FromImage; + testTag = icp.Tag; + }) .Returns(TaskEx.Done); client.SetupGet(c => c.Images).Returns(images.Object); @@ -105,12 +96,14 @@ public async Task ImageNotFoundUnitTest() using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15))) { var images = new Mock(); - //ImagesCreateParameters parameters, AuthConfig authConfig, IProgress progress, CancellationToken cancellationToken = default(CancellationToken) - images.Setup(m => m.CreateImageAsync(It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new DockerApiException(System.Net.HttpStatusCode.NotFound, "FakeResponseBody")); + // ImagesCreateParameters parameters, AuthConfig authConfig, IProgress progress, CancellationToken cancellationToken = default(CancellationToken) + images.Setup( + m => m.CreateImageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(new DockerApiException(HttpStatusCode.NotFound, "FakeResponseBody")); var dockerClient = new Mock(); dockerClient.SetupGet(c => c.Images).Returns(images.Object); @@ -130,12 +123,14 @@ public async Task InternalServerErrorUnitTest() using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15))) { var images = new Mock(); - //ImagesCreateParameters parameters, AuthConfig authConfig, IProgress progress, CancellationToken cancellationToken = default(CancellationToken) - images.Setup(m => m.CreateImageAsync(It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new DockerApiException(System.Net.HttpStatusCode.InternalServerError, "FakeResponseBody")); + // ImagesCreateParameters parameters, AuthConfig authConfig, IProgress progress, CancellationToken cancellationToken = default(CancellationToken) + images.Setup( + m => m.CreateImageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(new DockerApiException(HttpStatusCode.InternalServerError, "FakeResponseBody")); var dockerClient = new Mock(); dockerClient.SetupGet(c => c.Images).Returns(images.Object); @@ -155,12 +150,14 @@ public async Task DockerApiExceptionDifferentFromNotFoundUnitTest() using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15))) { var images = new Mock(); - //ImagesCreateParameters parameters, AuthConfig authConfig, IProgress progress, CancellationToken cancellationToken = default(CancellationToken) - images.Setup(m => m.CreateImageAsync(It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new DockerApiException(System.Net.HttpStatusCode.Unauthorized, "FakeResponseBody")); + // ImagesCreateParameters parameters, AuthConfig authConfig, IProgress progress, CancellationToken cancellationToken = default(CancellationToken) + images.Setup( + m => m.CreateImageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(new DockerApiException(HttpStatusCode.Unauthorized, "FakeResponseBody")); var dockerClient = new Mock(); dockerClient.SetupGet(c => c.Images).Returns(images.Object); @@ -171,5 +168,17 @@ public async Task DockerApiExceptionDifferentFromNotFoundUnitTest() await Assert.ThrowsAsync(() => pullCommand.ExecuteAsync(cts.Token)); } } + + static IEnumerable CreateTestData() + { + (string testFullImage, string image, string tag)[] testInputRecords = + { + ("localhost:5000/edge-hub:latest", "localhost:5000/edge-hub", "latest"), + ("edgebuilds.azurecr.io/azedge-edge-agent-x64:latest", "edgebuilds.azurecr.io/azedge-edge-agent-x64", "latest"), + ("mongo:3.4.4", "mongo", "3.4.4"), + ("edgebuilds.azurecr.io/azedge-simulated-temperature-sensor-x64", "edgebuilds.azurecr.io/azedge-simulated-temperature-sensor-x64", string.Empty) + }; + return testInputRecords.Select(r => new object[] { r.testFullImage, r.image, r.tag }).AsEnumerable(); + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs index e7bf2244062..2a9fcd1f8f2 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/CombinedEdgeletConfigProviderTest.cs @@ -35,19 +35,18 @@ public void TestVolMount() var unixUris = new Dictionary { - {Constants.EdgeletWorkloadUriVariableName, "unix:///path/to/workload.sock" }, - {Constants.EdgeletManagementUriVariableName, "unix:///path/to/mgmt.sock" } + { Constants.EdgeletWorkloadUriVariableName, "unix:///path/to/workload.sock" }, + { Constants.EdgeletManagementUriVariableName, "unix:///path/to/mgmt.sock" } }; var windowsUris = new Dictionary { - {Constants.EdgeletWorkloadUriVariableName, "unix:///C:/path/to/workload/sock" }, - {Constants.EdgeletManagementUriVariableName, "unix:///C:/path/to/mgmt/sock" } + { Constants.EdgeletWorkloadUriVariableName, "unix:///C:/path/to/workload/sock" }, + { Constants.EdgeletManagementUriVariableName, "unix:///C:/path/to/mgmt/sock" } }; IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windowsUris : unixUris - ).Build(); + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windowsUris : unixUris).Build(); var configSource = Mock.Of(s => s.Configuration == configRoot); ICombinedConfigProvider provider = new CombinedEdgeletConfigProvider(new[] { new AuthConfig() }, configSource); @@ -63,7 +62,9 @@ public void TestVolMount() { Assert.Equal("C:\\path\\to\\workload:C:\\path\\to\\workload", config.CreateOptions.HostConfig.Binds[0]); Assert.Equal("C:\\path\\to\\mgmt:C:\\path\\to\\mgmt", config.CreateOptions.HostConfig.Binds[1]); - } else { + } + else + { Assert.Equal("/path/to/workload.sock:/path/to/workload.sock", config.CreateOptions.HostConfig.Binds[0]); Assert.Equal("/path/to/mgmt.sock:/path/to/mgmt.sock", config.CreateOptions.HostConfig.Binds[1]); } @@ -83,8 +84,8 @@ public void TestNoVolMountForNonUds() IConfigurationRoot configRoot = new ConfigurationBuilder().AddInMemoryCollection( new Dictionary { - {Constants.EdgeletWorkloadUriVariableName, "http://localhost:2375/" }, - {Constants.EdgeletManagementUriVariableName, "http://localhost:2376/" } + { Constants.EdgeletWorkloadUriVariableName, "http://localhost:2375/" }, + { Constants.EdgeletManagementUriVariableName, "http://localhost:2376/" } }).Build(); var configSource = Mock.Of(s => s.Configuration == configRoot); ICombinedConfigProvider provider = new CombinedEdgeletConfigProvider(new[] { new AuthConfig() }, configSource); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test.csproj index d10ec8b877d..dbea6d0eba4 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test.csproj +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Docker.Test.csproj @@ -16,4 +16,12 @@ + + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/EdgeletFixture.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/EdgeletFixture.cs index 2472b6c7715..bd91682e1f8 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/EdgeletFixture.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/EdgeletFixture.cs @@ -6,12 +6,21 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; - using Startup = TestServer.Startup; + using Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test.TestServer; public class EdgeletFixture : IDisposable { - const int DefaultPort = 50002; public string ServiceUrl = ServiceHost.Instance.Url; + const int DefaultPort = 50002; + + #region IDisposable Support + + // Don't dispose the Server, in case another test thread is using it. + public void Dispose() + { + } + + #endregion class ServiceHost { @@ -20,7 +29,7 @@ class ServiceHost ServiceHost() { - this.webHostTask = BuildWebHost(new string[0], DefaultPort).RunAsync(cancellationTokenSource.Token); + this.webHostTask = BuildWebHost(new string[0], DefaultPort).RunAsync(this.cancellationTokenSource.Token); this.Url = $"http://localhost:{DefaultPort}"; } @@ -30,15 +39,9 @@ class ServiceHost static IWebHost BuildWebHost(string[] args, int port) => WebHost.CreateDefaultBuilder(args) - .UseUrls($"http://*:{port}") - .UseStartup() - .Build(); + .UseUrls($"http://*:{port}") + .UseStartup() + .Build(); } - - #region IDisposable Support - // Don't dispose the Server, in case another test thread is using it. - public void Dispose() - { } - #endregion } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test.csproj index fc96761afed..b0695048c5b 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test.csproj +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test.csproj @@ -40,4 +40,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleIdentityLifecycleManagerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleIdentityLifecycleManagerTest.cs index 3a92540c39e..2728d5c6542 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleIdentityLifecycleManagerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleIdentityLifecycleManagerTest.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Agent.Core.Test; - using Microsoft.Azure.Devices.Edge.Agent.Edgelet; using Microsoft.Azure.Devices.Edge.Agent.Edgelet.GeneratedCode; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; @@ -77,16 +76,18 @@ public async Task TestGetModulesIdentity_WithNewModules_ShouldCreateIdentities() GenerationId = Guid.NewGuid().ToString() }; - var identityManager = Mock.Of(m => - m.GetIdentities() == Task.FromResult(Enumerable.Empty()) && - m.CreateIdentityAsync(Name, Constants.ModuleIdentityEdgeManagedByValue) == Task.FromResult(identity)); + var identityManager = Mock.Of( + m => + m.GetIdentities() == Task.FromResult(Enumerable.Empty()) && + m.CreateIdentityAsync(Name, Constants.ModuleIdentityEdgeManagedByValue) == Task.FromResult(identity)); var moduleIdentityLifecycleManager = new ModuleIdentityLifecycleManager(identityManager, ModuleIdentityProviderServiceBuilder, EdgeletUri); var module = new TestModule(Name, "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, new Dictionary()); // Act IImmutableDictionary modulesIdentities = await moduleIdentityLifecycleManager.GetModuleIdentitiesAsync( - ModuleSet.Create(new IModule[] { module }), ModuleSet.Empty); + ModuleSet.Create(new IModule[] { module }), + ModuleSet.Empty); // Assert Assert.True(modulesIdentities.Count() == 1); @@ -144,12 +145,13 @@ public async Task TestGetModulesIdentity_WithUpdatedModules_ShouldUpdateIdentiti GenerationId = Guid.NewGuid().ToString() }; - var identityManager = Mock.Of(m => - m.GetIdentities() == Task.FromResult(new List() { identity2, identity3, identity4, identity5 }.AsEnumerable()) && - m.CreateIdentityAsync(Module1, Constants.ModuleIdentityEdgeManagedByValue) == Task.FromResult(identity1) && - m.UpdateIdentityAsync(identity2.ModuleId, identity2.GenerationId, identity2.ManagedBy) == Task.FromResult(identity2) && - m.UpdateIdentityAsync(identity3.ModuleId, identity3.GenerationId, identity3.ManagedBy) == Task.FromResult(identity3) && - m.UpdateIdentityAsync(identity4.ModuleId, identity4.GenerationId, identity4.ManagedBy) == Task.FromResult(identity4)); + var identityManager = Mock.Of( + m => + m.GetIdentities() == Task.FromResult(new List() { identity2, identity3, identity4, identity5 }.AsEnumerable()) && + m.CreateIdentityAsync(Module1, Constants.ModuleIdentityEdgeManagedByValue) == Task.FromResult(identity1) && + m.UpdateIdentityAsync(identity2.ModuleId, identity2.GenerationId, identity2.ManagedBy) == Task.FromResult(identity2) && + m.UpdateIdentityAsync(identity3.ModuleId, identity3.GenerationId, identity3.ManagedBy) == Task.FromResult(identity3) && + m.UpdateIdentityAsync(identity4.ModuleId, identity4.GenerationId, identity4.ManagedBy) == Task.FromResult(identity4)); var moduleIdentityLifecycleManager = new ModuleIdentityLifecycleManager(identityManager, ModuleIdentityProviderServiceBuilder, EdgeletUri); var envVar = new Dictionary(); @@ -207,10 +209,11 @@ public async Task TestGetModulesIdentity_WithRemovedModules_ShouldRemove() GenerationId = Guid.NewGuid().ToString() }; - var identityManager = Mock.Of(m => - m.GetIdentities() == Task.FromResult(new List() { identity2, identity3 }.AsEnumerable()) && - m.CreateIdentityAsync(Module1, It.IsAny()) == Task.FromResult(identity1) && - m.DeleteIdentityAsync(Module3) == Task.FromResult(identity3)); + var identityManager = Mock.Of( + m => + m.GetIdentities() == Task.FromResult(new List() { identity2, identity3 }.AsEnumerable()) && + m.CreateIdentityAsync(Module1, It.IsAny()) == Task.FromResult(identity1) && + m.DeleteIdentityAsync(Module3) == Task.FromResult(identity3)); var moduleIdentityLifecycleManager = new ModuleIdentityLifecycleManager(identityManager, ModuleIdentityProviderServiceBuilder, EdgeletUri); var envVar = new Dictionary(); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleManagementHttpClientTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleManagementHttpClientTest.cs index 4ac9771f535..5e0feffb8f5 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleManagementHttpClientTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/ModuleManagementHttpClientTest.cs @@ -3,6 +3,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -47,7 +48,7 @@ public async Task IdentityTest() // Act Identity identity4 = await client.UpdateIdentityAsync("Foo", identity1.GenerationId, identity1.ManagedBy); - Identity identity5 = await client.UpdateIdentityAsync("Bar", identity2.GenerationId, identity2.ManagedBy); + Identity identity5 = await client.UpdateIdentityAsync("Bar", identity2.GenerationId, identity2.ManagedBy); Identity identity6 = await client.UpdateIdentityAsync("External", identity3.GenerationId, identity3.ManagedBy); // Assert @@ -75,7 +76,7 @@ public async Task IdentityTest() Assert.Equal("Bar", identities[0].ModuleId); Assert.Equal("External", identities[1].ModuleId); Assert.Equal("Foo", identities[2].ModuleId); - Assert.Equal(Constants.ModuleIdentityEdgeManagedByValue, identities[0].ManagedBy); + Assert.Equal(Constants.ModuleIdentityEdgeManagedByValue, identities[0].ManagedBy); Assert.Equal("Someone", identities[1].ManagedBy); Assert.Equal(Constants.ModuleIdentityEdgeManagedByValue, identities[2].ManagedBy); @@ -103,7 +104,7 @@ public async Task ModulesTest() Type = "Docker", Config = new Config { - Env = new System.Collections.ObjectModel.ObservableCollection { new EnvVar { Key = "E1", Value = "P1" } }, + Env = new ObservableCollection { new EnvVar { Key = "E1", Value = "P1" } }, Settings = "{ \"image\": \"testimage\" }" } }; diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/RuntimeInfoProviderTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/RuntimeInfoProviderTest.cs index 823cbb47c63..c36ffd59f3b 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/RuntimeInfoProviderTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/RuntimeInfoProviderTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test using Moq; using Newtonsoft.Json.Linq; using Xunit; - using EnvVar = Microsoft.Azure.Devices.Edge.Agent.Edgelet.GeneratedCode.EnvVar; + using SystemInfo = Microsoft.Azure.Devices.Edge.Agent.Edgelet.GeneratedCode.SystemInfo; [Unit] public class RuntimeInfoProviderTest @@ -22,10 +22,10 @@ public class RuntimeInfoProviderTest public async Task GetSystemInfoTest() { // Arrange - var systemInfoSample = new GeneratedCode.SystemInfo(); + var systemInfoSample = new SystemInfo(); systemInfoSample.OsType = "linux"; systemInfoSample.Architecture = "x86"; - + var moduleManager = Mock.Of(m => m.GetSystemInfoAsync() == Task.FromResult(systemInfoSample)); IRuntimeInfoProvider runtimeInfoProvider = new RuntimeInfoProvider(moduleManager); diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/EdgeletTestImplementation.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/EdgeletTestImplementation.cs index d4e07cebdd4..e115f39c531 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/EdgeletTestImplementation.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/EdgeletTestImplementation.cs @@ -47,25 +47,13 @@ public Task CreateModuleAsync(string apiVersion, ModuleSpec modul return Task.FromResult(createdModule); } - static ModuleDetails GetModuleDetails(ModuleSpec moduleSpec) - { - var moduleDetails = new ModuleDetails - { - Id = Guid.NewGuid().ToString(), - Name = moduleSpec.Name, - Type = moduleSpec.Type, - Status = new Status { ExitStatus = null, RuntimeStatus = new RuntimeStatus { Status = "Created", Description = "Created" }, StartTime = null }, - Config = moduleSpec.Config - }; - return moduleDetails; - } - public Task DeleteIdentityAsync(string apiVersion, string name) { if (!this.identities.TryRemove(name, out Identity _)) { throw new InvalidOperationException("Identity not found"); } + return Task.CompletedTask; } @@ -75,6 +63,7 @@ public Task DeleteModuleAsync(string apiVersion, string name) { throw new InvalidOperationException("Module not found"); } + return Task.CompletedTask; } @@ -84,6 +73,7 @@ public Task GetModuleAsync(string apiVersion, string name) { throw new InvalidOperationException("Module not found"); } + return Task.FromResult(module); } @@ -99,6 +89,7 @@ public Task RestartModuleAsync(string apiVersion, string name) { throw new InvalidOperationException("Module not found"); } + module.Status.RuntimeStatus.Status = "Running"; return Task.CompletedTask; } @@ -109,6 +100,7 @@ public Task StartModuleAsync(string apiVersion, string name) { throw new InvalidOperationException("Module not found"); } + module.Status.RuntimeStatus.Status = "Running"; return Task.CompletedTask; } @@ -119,6 +111,7 @@ public Task StopModuleAsync(string apiVersion, string name) { throw new InvalidOperationException("Module not found"); } + if (module.Status.RuntimeStatus.Status == "Stopped") { return Task.FromResult(304); @@ -136,8 +129,22 @@ public Task UpdateModuleAsync(string apiVersion, string name, boo { throw new InvalidOperationException("Module not found"); } + this.modules[module.Name] = GetModuleDetails(module); return Task.FromResult(this.modules[module.Name]); } + + static ModuleDetails GetModuleDetails(ModuleSpec moduleSpec) + { + var moduleDetails = new ModuleDetails + { + Id = Guid.NewGuid().ToString(), + Name = moduleSpec.Name, + Type = moduleSpec.Type, + Status = new Status { ExitStatus = null, RuntimeStatus = new RuntimeStatus { Status = "Created", Description = "Created" }, StartTime = null }, + Config = moduleSpec.Config + }; + return moduleDetails; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/Startup.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/Startup.cs index f98c46f515d..143a1fe3f1f 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/Startup.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.Edgelet.Test/TestServer/Startup.cs @@ -11,7 +11,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/AgentStateTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/AgentStateTest.cs index ed028682c83..33d4b904909 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/AgentStateTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/AgentStateTest.cs @@ -10,6 +10,21 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test public class AgentStateTest { + [Theory] + [Unit] + [MemberData(nameof(GetValidJsonInputs))] + public void DeserializeJsonTest(object input, AgentState expected) + { + // Arrange + string json = JsonConvert.SerializeObject(input); + + // Act + var state = JsonConvert.DeserializeObject(json); + + // Assert + Assert.Equal(expected, state); + } + static IEnumerable GetValidJsonInputs() { (object input, AgentState expected)[] inputs = @@ -27,7 +42,7 @@ static IEnumerable GetValidJsonInputs() lastDesiredStatus = new { code = 200, - description = "" + description = string.Empty } }, new AgentState(10, DeploymentStatus.Success)), @@ -36,7 +51,7 @@ static IEnumerable GetValidJsonInputs() lastDesiredStatus = new { code = 200, - description = "" + description = string.Empty } }, new AgentState(0, DeploymentStatus.Success)), @@ -47,27 +62,12 @@ static IEnumerable GetValidJsonInputs() lastDesiredStatus = new { code = 200, - description = "" + description = string.Empty } }, new AgentState(10, DeploymentStatus.Success, schemaVersion: "2.0")) }; return inputs.Select(r => new[] { r.input, r.expected }); } - - [Theory] - [Unit] - [MemberData(nameof(GetValidJsonInputs))] - public void DeserializeJsonTest(object input, AgentState expected) - { - // Arrange - string json = JsonConvert.SerializeObject(input); - - // Act - var state = JsonConvert.DeserializeObject(json); - - // Assert - Assert.Equal(expected, state); - } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/EdgeAgentConnectionTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/EdgeAgentConnectionTest.cs index 35a4df5c612..062324859c1 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/EdgeAgentConnectionTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/EdgeAgentConnectionTest.cs @@ -21,156 +21,12 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test using Xunit; using IotHubConnectionStringBuilder = Microsoft.Azure.Devices.IotHubConnectionStringBuilder; using ModuleClient = Microsoft.Azure.Devices.Edge.Agent.IoTHub.ModuleClient; + using ServiceClient = Microsoft.Azure.Devices.ServiceClient; public class EdgeAgentConnectionTest { const string DockerType = "docker"; - [Integration] - [Fact] - public async Task EdgeAgentConnectionBasicTest() - { - string iotHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); - IotHubConnectionStringBuilder iotHubConnectionStringBuilder = IotHubConnectionStringBuilder.Create(iotHubConnectionString); - RegistryManager registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString); - await registryManager.OpenAsync(); - - string edgeDeviceId = "testMmaEdgeDevice1" + Guid.NewGuid().ToString(); - - var edgeDevice = new Device(edgeDeviceId) - { - Capabilities = new DeviceCapabilities { IotEdge = true }, - Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas } - }; - - try - { - edgeDevice = await registryManager.AddDeviceAsync(edgeDevice); - - await SetAgentDesiredProperties(registryManager, edgeDeviceId); - - string edgeAgentConnectionString = $"HostName={iotHubConnectionStringBuilder.HostName};DeviceId={edgeDeviceId};ModuleId=$edgeAgent;SharedAccessKey={edgeDevice.Authentication.SymmetricKey.PrimaryKey}"; - IModuleClientProvider moduleClientProvider = new ModuleClientProvider(edgeAgentConnectionString, Option.None(), Option.None()); - - var moduleDeserializerTypes = new Dictionary - { - { DockerType, typeof(DockerDesiredModule) } - }; - - var edgeAgentDeserializerTypes = new Dictionary - { - { DockerType, typeof(EdgeAgentDockerModule) } - }; - - var edgeHubDeserializerTypes = new Dictionary - { - { DockerType, typeof(EdgeHubDockerModule) } - }; - - var runtimeInfoDeserializerTypes = new Dictionary - { - { DockerType, typeof(DockerRuntimeInfo) } - }; - - var deserializerTypes = new Dictionary> - { - [typeof(IModule)] = moduleDeserializerTypes, - [typeof(IEdgeAgentModule)] = edgeAgentDeserializerTypes, - [typeof(IEdgeHubModule)] = edgeHubDeserializerTypes, - [typeof(IRuntimeInfo)] = runtimeInfoDeserializerTypes, - }; - - ISerde serde = new TypeSpecificSerDe(deserializerTypes); - IEdgeAgentConnection edgeAgentConnection = new EdgeAgentConnection(moduleClientProvider, serde); - await Task.Delay(TimeSpan.FromSeconds(10)); - - Option deploymentConfigInfo = await edgeAgentConnection.GetDeploymentConfigInfoAsync(); - - Assert.True(deploymentConfigInfo.HasValue); - DeploymentConfig deploymentConfig = deploymentConfigInfo.OrDefault().DeploymentConfig; - Assert.NotNull(deploymentConfig); - Assert.NotNull(deploymentConfig.Modules); - Assert.NotNull(deploymentConfig.Runtime); - Assert.NotNull(deploymentConfig.SystemModules); - Assert.Equal(EdgeAgentConnection.ExpectedSchemaVersion.ToString(), deploymentConfig.SchemaVersion); - Assert.NotNull(deploymentConfig.SystemModules.EdgeAgent); - Assert.NotNull(deploymentConfig.SystemModules.EdgeHub); - Assert.Equal(1, deploymentConfig.Modules.Count); - Assert.NotNull(deploymentConfig.Modules["mongoserver"]); - ValidateRuntimeConfig(deploymentConfig.Runtime); - ValidateModules(deploymentConfig); - - await UpdateAgentDesiredProperties(registryManager, edgeDeviceId); - await Task.Delay(TimeSpan.FromSeconds(10)); - - deploymentConfigInfo = await edgeAgentConnection.GetDeploymentConfigInfoAsync(); - - Assert.True(deploymentConfigInfo.HasValue); - deploymentConfig = deploymentConfigInfo.OrDefault().DeploymentConfig; - Assert.NotNull(deploymentConfig); - Assert.NotNull(deploymentConfig.Modules); - Assert.NotNull(deploymentConfig.Runtime); - Assert.NotNull(deploymentConfig.SystemModules); - Assert.Equal(EdgeAgentConnection.ExpectedSchemaVersion.ToString(), deploymentConfig.SchemaVersion); - Assert.NotNull(deploymentConfig.SystemModules.EdgeAgent); - Assert.NotNull(deploymentConfig.SystemModules.EdgeHub); - Assert.Equal(2, deploymentConfig.Modules.Count); - Assert.NotNull(deploymentConfig.Modules["mongoserver"]); - Assert.NotNull(deploymentConfig.Modules["mlModule"]); - ValidateRuntimeConfig(deploymentConfig.Runtime); - } - finally - { - try - { - await registryManager.RemoveDeviceAsync(edgeDevice); - } - catch (Exception) - { - // ignored - } - } - } - - static void ValidateModules(DeploymentConfig deploymentConfig) - { - Assert.True(deploymentConfig.SystemModules.EdgeAgent.HasValue); - Assert.True(deploymentConfig.SystemModules.EdgeHub.HasValue); - - var edgeAgent = deploymentConfig.SystemModules.EdgeAgent.OrDefault() as EdgeAgentDockerModule; - Assert.NotNull(edgeAgent); - Assert.Equal(edgeAgent.Env["e1"].Value, "e1val"); - Assert.Equal(edgeAgent.Env["e2"].Value, "e2val"); - - var edgeHub = deploymentConfig.SystemModules.EdgeHub.OrDefault() as EdgeHubDockerModule; - Assert.NotNull(edgeHub); - Assert.Equal(edgeHub.Env["e3"].Value, "e3val"); - Assert.Equal(edgeHub.Env["e4"].Value, "e4val"); - - var module1 = deploymentConfig.Modules["mongoserver"] as DockerDesiredModule; - Assert.NotNull(module1); - Assert.Equal(module1.Env["e5"].Value, "e5val"); - Assert.Equal(module1.Env["e6"].Value, "e6val"); - } - - static void ValidateRuntimeConfig(IRuntimeInfo deploymentConfigRuntime) - { - var dockerRuntimeConfig = deploymentConfigRuntime as IRuntimeInfo; - Assert.NotNull(dockerRuntimeConfig); - - Assert.Null(dockerRuntimeConfig.Config.LoggingOptions); - Assert.Equal(2, dockerRuntimeConfig.Config.RegistryCredentials.Count); - RegistryCredentials r1 = dockerRuntimeConfig.Config.RegistryCredentials["r1"]; - Assert.Equal("acr1.azure.net", r1.Address); - Assert.Equal("u1", r1.Username); - Assert.Equal("p1", r1.Password); - - RegistryCredentials r2 = dockerRuntimeConfig.Config.RegistryCredentials["r2"]; - Assert.Equal("acr2.azure.net", r2.Address); - Assert.Equal("u2", r2.Username); - Assert.Equal("p2", r2.Password); - } - public static async Task SetAgentDesiredProperties(RegistryManager rm, string deviceId) { var dp = new @@ -221,7 +77,7 @@ public static async Task SetAgentDesiredProperties(RegistryManager rm, string de settings = new { image = "edgeAgent", - createOptions = "" + createOptions = string.Empty } }, edgeHub = new @@ -243,7 +99,7 @@ public static async Task SetAgentDesiredProperties(RegistryManager rm, string de settings = new { image = "edgeHub", - createOptions = "" + createOptions = string.Empty } } }, @@ -269,7 +125,7 @@ public static async Task SetAgentDesiredProperties(RegistryManager rm, string de settings = new { image = "mongo", - createOptions = "" + createOptions = string.Empty } } } @@ -281,190 +137,69 @@ public static async Task SetAgentDesiredProperties(RegistryManager rm, string de ["$edgeAgent"] = new Dictionary { ["properties.desired"] = dp - } } }; await rm.ApplyConfigurationContentOnDeviceAsync(deviceId, cc); } - [Integration] - [Fact] - public async Task EdgeAgentConnectionConfigurationTest() + public static async Task CreateConfigurationAsync(RegistryManager registryMananger, string configurationId, string targetCondition, int priority) { - string edgeDeviceId = "testMmaEdgeDevice1" + Guid.NewGuid().ToString(); - string configurationId = "testconfiguration-" + Guid.NewGuid().ToString(); - string conditionPropertyName = "condition-" + Guid.NewGuid().ToString("N"); - string conditionPropertyValue = Guid.NewGuid().ToString(); - string iotHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); - IotHubConnectionStringBuilder iotHubConnectionStringBuilder = IotHubConnectionStringBuilder.Create(iotHubConnectionString); - RegistryManager registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString); - - try + var configuration = new Configuration(configurationId) { - await registryManager.OpenAsync(); - - var edgeDevice = new Device(edgeDeviceId) + Labels = new Dictionary { - Capabilities = new DeviceCapabilities { IotEdge = true }, - Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas } - }; - edgeDevice = await registryManager.AddDeviceAsync(edgeDevice); - - Twin twin = await registryManager.GetTwinAsync(edgeDeviceId); - twin.Tags[conditionPropertyName] = conditionPropertyValue; - await registryManager.UpdateTwinAsync(edgeDeviceId, twin, twin.ETag); - await registryManager.GetTwinAsync(edgeDeviceId, "$edgeAgent"); - await registryManager.GetTwinAsync(edgeDeviceId, "$edgeHub"); - - await CreateConfigurationAsync(registryManager, configurationId, $"tags.{conditionPropertyName}='{conditionPropertyValue}'", 10); - - // Service takes about 5 mins to sync config to twin - await Task.Delay(TimeSpan.FromMinutes(7)); + { "App", "Stream Analytics" } + }, + Content = GetDefaultConfigurationContent(), + Priority = priority, + TargetCondition = targetCondition + }; - string edgeAgentConnectionString = $"HostName={iotHubConnectionStringBuilder.HostName};DeviceId={edgeDeviceId};ModuleId=$edgeAgent;SharedAccessKey={edgeDevice.Authentication.SymmetricKey.PrimaryKey}"; - IModuleClientProvider moduleClientProvider = new ModuleClientProvider(edgeAgentConnectionString, Option.None(), Option.None()); + return await registryMananger.AddConfigurationAsync(configuration); + } - var moduleDeserializerTypes = new Dictionary - { - { DockerType, typeof(DockerDesiredModule) } - }; + public static async Task DeleteConfigurationAsync(RegistryManager registryManager, string configurationId) + { + await registryManager.RemoveConfigurationAsync(configurationId); + } - var edgeAgentDeserializerTypes = new Dictionary + public static TwinCollection GetEdgeAgentReportedProperties(DeploymentConfigInfo deploymentConfigInfo) + { + DeploymentConfig deploymentConfig = deploymentConfigInfo.DeploymentConfig; + var reportedProperties = new + { + lastDesiredVersion = deploymentConfigInfo.Version, + lastDesiredStatus = new { - { DockerType, typeof(EdgeAgentDockerModule) } - }; - - var edgeHubDeserializerTypes = new Dictionary + code = 200 + }, + runtime = new { - { DockerType, typeof(EdgeHubDockerModule) } - }; - - var runtimeInfoDeserializerTypes = new Dictionary + type = "docker" + }, + systemModules = new { - { DockerType, typeof(DockerRuntimeInfo) } - }; - - var deserializerTypes = new Dictionary> - { - [typeof(IModule)] = moduleDeserializerTypes, - [typeof(IEdgeAgentModule)] = edgeAgentDeserializerTypes, - [typeof(IEdgeHubModule)] = edgeHubDeserializerTypes, - [typeof(IRuntimeInfo)] = runtimeInfoDeserializerTypes, - }; - - ISerde serde = new TypeSpecificSerDe(deserializerTypes); - IEdgeAgentConnection edgeAgentConnection = new EdgeAgentConnection(moduleClientProvider, serde); - await Task.Delay(TimeSpan.FromSeconds(20)); - - Option deploymentConfigInfo = await edgeAgentConnection.GetDeploymentConfigInfoAsync(); - - Assert.True(deploymentConfigInfo.HasValue); - DeploymentConfig deploymentConfig = deploymentConfigInfo.OrDefault().DeploymentConfig; - Assert.NotNull(deploymentConfig); - Assert.NotNull(deploymentConfig.Modules); - Assert.NotNull(deploymentConfig.Runtime); - Assert.NotNull(deploymentConfig.SystemModules); - Assert.Equal(EdgeAgentConnection.ExpectedSchemaVersion.ToString(), deploymentConfig.SchemaVersion); - Assert.NotNull(deploymentConfig.SystemModules.EdgeAgent); - Assert.NotNull(deploymentConfig.SystemModules.EdgeHub); - Assert.Equal(2, deploymentConfig.Modules.Count); - Assert.NotNull(deploymentConfig.Modules["mongoserver"]); - Assert.NotNull(deploymentConfig.Modules["asa"]); - - TwinCollection reportedPatch = GetEdgeAgentReportedProperties(deploymentConfigInfo.OrDefault()); - await edgeAgentConnection.UpdateReportedPropertiesAsync(reportedPatch); - - // Service takes about 5 mins to sync statistics to config - await Task.Delay(TimeSpan.FromMinutes(7)); - - Configuration config = await registryManager.GetConfigurationAsync(configurationId); - Assert.NotNull(config); - Assert.NotNull(config.SystemMetrics); - Assert.True(config.SystemMetrics.Results.ContainsKey("targetedCount")); - Assert.Equal(1, config.SystemMetrics.Results["targetedCount"]); - Assert.True(config.SystemMetrics.Results.ContainsKey("appliedCount")); - Assert.Equal(1, config.SystemMetrics.Results["appliedCount"]); - } - finally - { - try - { - await registryManager.RemoveDeviceAsync(edgeDeviceId); - } - catch (Exception) - { - // ignored - } - - try - { - await DeleteConfigurationAsync(registryManager, configurationId); - } - catch (Exception) - { - // ignored - } - } - } - - public static async Task CreateConfigurationAsync(RegistryManager registryMananger, string configurationId, string targetCondition, int priority) - { - var configuration = new Configuration(configurationId) - { - Labels = new Dictionary - { - { "App", "Stream Analytics" } - }, - Content = GetDefaultConfigurationContent(), - Priority = priority, - TargetCondition = targetCondition - }; - - return await registryMananger.AddConfigurationAsync(configuration); - } - - public static async Task DeleteConfigurationAsync(RegistryManager registryManager, string configurationId) - { - await registryManager.RemoveConfigurationAsync(configurationId); - } - - public static TwinCollection GetEdgeAgentReportedProperties(DeploymentConfigInfo deploymentConfigInfo) - { - DeploymentConfig deploymentConfig = deploymentConfigInfo.DeploymentConfig; - var reportedProperties = new - { - lastDesiredVersion = deploymentConfigInfo.Version, - lastDesiredStatus = new - { - code = 200 - }, - runtime = new - { - type = "docker" - }, - systemModules = new - { - edgeAgent = new - { - runtimeStatus = "running", - description = "All good", - configuration = new - { - id = deploymentConfig.SystemModules.EdgeAgent.OrDefault().ConfigurationInfo.Id - } - }, - edgeHub = new - { - runtimeStatus = "running", - description = "All good", - configuration = new - { - id = deploymentConfig.SystemModules.EdgeHub.OrDefault().ConfigurationInfo.Id - } - } - }, - modules = new + edgeAgent = new + { + runtimeStatus = "running", + description = "All good", + configuration = new + { + id = deploymentConfig.SystemModules.EdgeAgent.OrDefault().ConfigurationInfo.Id + } + }, + edgeHub = new + { + runtimeStatus = "running", + description = "All good", + configuration = new + { + id = deploymentConfig.SystemModules.EdgeHub.OrDefault().ConfigurationInfo.Id + } + } + }, + modules = new { mongoserver = new { @@ -517,9 +252,9 @@ public static ConfigurationContent GetDefaultConfigurationContent() }; } - static object GetEdgeAgentConfiguration() + public static async Task UpdateAgentDesiredProperties(RegistryManager rm, string deviceId) { - var desiredProperties = new + var dp = new { schemaVersion = "1.0", runtime = new @@ -527,18 +262,36 @@ static object GetEdgeAgentConfiguration() type = "docker", settings = new { - loggingOptions = "" + registryCredentials = new + { + r1 = new + { + address = "acr1.azure.net", + username = "u1", + password = "p1" + }, + r2 = new + { + address = "acr2.azure.net", + username = "u2", + password = "p2" + } + } } }, systemModules = new { edgeAgent = new { + configuration = new + { + id = "1235" + }, type = "docker", settings = new { image = "edgeAgent", - createOptions = "" + createOptions = string.Empty } }, edgeHub = new @@ -549,7 +302,7 @@ static object GetEdgeAgentConfiguration() settings = new { image = "edgeHub", - createOptions = "" + createOptions = string.Empty } } }, @@ -561,162 +314,276 @@ static object GetEdgeAgentConfiguration() type = "docker", status = "running", restartPolicy = "on-failure", + env = new + { + e5 = new + { + value = "e5val" + }, + e7 = new + { + value = "e7val" + } + }, settings = new { image = "mongo", - createOptions = "" + createOptions = string.Empty } }, - asa = new + mlModule = new { version = "1.0", type = "docker", status = "running", - restartPolicy = "on-failure", + restartPolicy = "on-unhealthy", settings = new { - image = "asa", - createOptions = "" + image = "ml:latest", + createOptions = string.Empty } } } }; - return desiredProperties; - } - static object GetEdgeHubConfiguration() - { - var desiredProperties = new + var cc = new ConfigurationContent { - schemaVersion = "1.0", - routes = new Dictionary - { - ["route1"] = "from /* INTO $upstream", - }, - storeAndForwardConfiguration = new + ModulesContent = new Dictionary> { - timeToLiveSecs = 20 + ["$edgeAgent"] = new Dictionary + { + ["properties.desired"] = dp + } } }; - return desiredProperties; + + await rm.ApplyConfigurationContentOnDeviceAsync(deviceId, cc); } - static object GetTwinConfiguration(string moduleName) + [Integration] + [Fact] + public async Task EdgeAgentConnectionBasicTest() { - var desiredProperties = new + string iotHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); + IotHubConnectionStringBuilder iotHubConnectionStringBuilder = IotHubConnectionStringBuilder.Create(iotHubConnectionString); + RegistryManager registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString); + await registryManager.OpenAsync(); + + string edgeDeviceId = "testMmaEdgeDevice1" + Guid.NewGuid().ToString(); + + var edgeDevice = new Device(edgeDeviceId) { - name = moduleName + Capabilities = new DeviceCapabilities { IotEdge = true }, + Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas } }; - return desiredProperties; + + try + { + edgeDevice = await registryManager.AddDeviceAsync(edgeDevice); + + await SetAgentDesiredProperties(registryManager, edgeDeviceId); + + string edgeAgentConnectionString = $"HostName={iotHubConnectionStringBuilder.HostName};DeviceId={edgeDeviceId};ModuleId=$edgeAgent;SharedAccessKey={edgeDevice.Authentication.SymmetricKey.PrimaryKey}"; + IModuleClientProvider moduleClientProvider = new ModuleClientProvider(edgeAgentConnectionString, Option.None(), Option.None()); + + var moduleDeserializerTypes = new Dictionary + { + { DockerType, typeof(DockerDesiredModule) } + }; + + var edgeAgentDeserializerTypes = new Dictionary + { + { DockerType, typeof(EdgeAgentDockerModule) } + }; + + var edgeHubDeserializerTypes = new Dictionary + { + { DockerType, typeof(EdgeHubDockerModule) } + }; + + var runtimeInfoDeserializerTypes = new Dictionary + { + { DockerType, typeof(DockerRuntimeInfo) } + }; + + var deserializerTypes = new Dictionary> + { + [typeof(IModule)] = moduleDeserializerTypes, + [typeof(IEdgeAgentModule)] = edgeAgentDeserializerTypes, + [typeof(IEdgeHubModule)] = edgeHubDeserializerTypes, + [typeof(IRuntimeInfo)] = runtimeInfoDeserializerTypes, + }; + + ISerde serde = new TypeSpecificSerDe(deserializerTypes); + IEdgeAgentConnection edgeAgentConnection = new EdgeAgentConnection(moduleClientProvider, serde); + await Task.Delay(TimeSpan.FromSeconds(10)); + + Option deploymentConfigInfo = await edgeAgentConnection.GetDeploymentConfigInfoAsync(); + + Assert.True(deploymentConfigInfo.HasValue); + DeploymentConfig deploymentConfig = deploymentConfigInfo.OrDefault().DeploymentConfig; + Assert.NotNull(deploymentConfig); + Assert.NotNull(deploymentConfig.Modules); + Assert.NotNull(deploymentConfig.Runtime); + Assert.NotNull(deploymentConfig.SystemModules); + Assert.Equal(EdgeAgentConnection.ExpectedSchemaVersion.ToString(), deploymentConfig.SchemaVersion); + Assert.NotNull(deploymentConfig.SystemModules.EdgeAgent); + Assert.NotNull(deploymentConfig.SystemModules.EdgeHub); + Assert.Equal(1, deploymentConfig.Modules.Count); + Assert.NotNull(deploymentConfig.Modules["mongoserver"]); + ValidateRuntimeConfig(deploymentConfig.Runtime); + ValidateModules(deploymentConfig); + + await UpdateAgentDesiredProperties(registryManager, edgeDeviceId); + await Task.Delay(TimeSpan.FromSeconds(10)); + + deploymentConfigInfo = await edgeAgentConnection.GetDeploymentConfigInfoAsync(); + + Assert.True(deploymentConfigInfo.HasValue); + deploymentConfig = deploymentConfigInfo.OrDefault().DeploymentConfig; + Assert.NotNull(deploymentConfig); + Assert.NotNull(deploymentConfig.Modules); + Assert.NotNull(deploymentConfig.Runtime); + Assert.NotNull(deploymentConfig.SystemModules); + Assert.Equal(EdgeAgentConnection.ExpectedSchemaVersion.ToString(), deploymentConfig.SchemaVersion); + Assert.NotNull(deploymentConfig.SystemModules.EdgeAgent); + Assert.NotNull(deploymentConfig.SystemModules.EdgeHub); + Assert.Equal(2, deploymentConfig.Modules.Count); + Assert.NotNull(deploymentConfig.Modules["mongoserver"]); + Assert.NotNull(deploymentConfig.Modules["mlModule"]); + ValidateRuntimeConfig(deploymentConfig.Runtime); + } + finally + { + try + { + await registryManager.RemoveDeviceAsync(edgeDevice); + } + catch (Exception) + { + // ignored + } + } } - public static async Task UpdateAgentDesiredProperties(RegistryManager rm, string deviceId) + [Integration] + [Fact] + public async Task EdgeAgentConnectionConfigurationTest() { - var dp = new + string edgeDeviceId = "testMmaEdgeDevice1" + Guid.NewGuid().ToString(); + string configurationId = "testconfiguration-" + Guid.NewGuid().ToString(); + string conditionPropertyName = "condition-" + Guid.NewGuid().ToString("N"); + string conditionPropertyValue = Guid.NewGuid().ToString(); + string iotHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); + IotHubConnectionStringBuilder iotHubConnectionStringBuilder = IotHubConnectionStringBuilder.Create(iotHubConnectionString); + RegistryManager registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString); + + try { - schemaVersion = "1.0", - runtime = new + await registryManager.OpenAsync(); + + var edgeDevice = new Device(edgeDeviceId) { - type = "docker", - settings = new - { - registryCredentials = new - { - r1 = new - { - address = "acr1.azure.net", - username = "u1", - password = "p1" - }, - r2 = new - { - address = "acr2.azure.net", - username = "u2", - password = "p2" - } - } - } - }, - systemModules = new + Capabilities = new DeviceCapabilities { IotEdge = true }, + Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas } + }; + edgeDevice = await registryManager.AddDeviceAsync(edgeDevice); + + Twin twin = await registryManager.GetTwinAsync(edgeDeviceId); + twin.Tags[conditionPropertyName] = conditionPropertyValue; + await registryManager.UpdateTwinAsync(edgeDeviceId, twin, twin.ETag); + await registryManager.GetTwinAsync(edgeDeviceId, "$edgeAgent"); + await registryManager.GetTwinAsync(edgeDeviceId, "$edgeHub"); + + await CreateConfigurationAsync(registryManager, configurationId, $"tags.{conditionPropertyName}='{conditionPropertyValue}'", 10); + + // Service takes about 5 mins to sync config to twin + await Task.Delay(TimeSpan.FromMinutes(7)); + + string edgeAgentConnectionString = $"HostName={iotHubConnectionStringBuilder.HostName};DeviceId={edgeDeviceId};ModuleId=$edgeAgent;SharedAccessKey={edgeDevice.Authentication.SymmetricKey.PrimaryKey}"; + IModuleClientProvider moduleClientProvider = new ModuleClientProvider(edgeAgentConnectionString, Option.None(), Option.None()); + + var moduleDeserializerTypes = new Dictionary { - edgeAgent = new - { - configuration = new - { - id = "1235" - }, - type = "docker", - settings = new - { - image = "edgeAgent", - createOptions = "" - } - }, - edgeHub = new - { - type = "docker", - status = "running", - restartPolicy = "always", - settings = new - { - image = "edgeHub", - createOptions = "" - } - } - }, - modules = new + { DockerType, typeof(DockerDesiredModule) } + }; + + var edgeAgentDeserializerTypes = new Dictionary + { + { DockerType, typeof(EdgeAgentDockerModule) } + }; + + var edgeHubDeserializerTypes = new Dictionary + { + { DockerType, typeof(EdgeHubDockerModule) } + }; + + var runtimeInfoDeserializerTypes = new Dictionary + { + { DockerType, typeof(DockerRuntimeInfo) } + }; + + var deserializerTypes = new Dictionary> { - mongoserver = new - { - version = "1.0", - type = "docker", - status = "running", - restartPolicy = "on-failure", - env = new - { - e5 = new - { - value = "e5val" - }, - e7 = new - { - value = "e7val" - } - }, - settings = new - { - image = "mongo", - createOptions = "" - } - }, - mlModule = new - { - version = "1.0", - type = "docker", - status = "running", - restartPolicy = "on-unhealthy", - settings = new - { - image = "ml:latest", - createOptions = "" - } - } - } - }; + [typeof(IModule)] = moduleDeserializerTypes, + [typeof(IEdgeAgentModule)] = edgeAgentDeserializerTypes, + [typeof(IEdgeHubModule)] = edgeHubDeserializerTypes, + [typeof(IRuntimeInfo)] = runtimeInfoDeserializerTypes, + }; - var cc = new ConfigurationContent + ISerde serde = new TypeSpecificSerDe(deserializerTypes); + IEdgeAgentConnection edgeAgentConnection = new EdgeAgentConnection(moduleClientProvider, serde); + await Task.Delay(TimeSpan.FromSeconds(20)); + + Option deploymentConfigInfo = await edgeAgentConnection.GetDeploymentConfigInfoAsync(); + + Assert.True(deploymentConfigInfo.HasValue); + DeploymentConfig deploymentConfig = deploymentConfigInfo.OrDefault().DeploymentConfig; + Assert.NotNull(deploymentConfig); + Assert.NotNull(deploymentConfig.Modules); + Assert.NotNull(deploymentConfig.Runtime); + Assert.NotNull(deploymentConfig.SystemModules); + Assert.Equal(EdgeAgentConnection.ExpectedSchemaVersion.ToString(), deploymentConfig.SchemaVersion); + Assert.NotNull(deploymentConfig.SystemModules.EdgeAgent); + Assert.NotNull(deploymentConfig.SystemModules.EdgeHub); + Assert.Equal(2, deploymentConfig.Modules.Count); + Assert.NotNull(deploymentConfig.Modules["mongoserver"]); + Assert.NotNull(deploymentConfig.Modules["asa"]); + + TwinCollection reportedPatch = GetEdgeAgentReportedProperties(deploymentConfigInfo.OrDefault()); + await edgeAgentConnection.UpdateReportedPropertiesAsync(reportedPatch); + + // Service takes about 5 mins to sync statistics to config + await Task.Delay(TimeSpan.FromMinutes(7)); + + Configuration config = await registryManager.GetConfigurationAsync(configurationId); + Assert.NotNull(config); + Assert.NotNull(config.SystemMetrics); + Assert.True(config.SystemMetrics.Results.ContainsKey("targetedCount")); + Assert.Equal(1, config.SystemMetrics.Results["targetedCount"]); + Assert.True(config.SystemMetrics.Results.ContainsKey("appliedCount")); + Assert.Equal(1, config.SystemMetrics.Results["appliedCount"]); + } + finally { - ModulesContent = new Dictionary> + try { - ["$edgeAgent"] = new Dictionary - { - ["properties.desired"] = dp - - } + await registryManager.RemoveDeviceAsync(edgeDeviceId); + } + catch (Exception) + { + // ignored } - }; - await rm.ApplyConfigurationContentOnDeviceAsync(deviceId, cc); + try + { + await DeleteConfigurationAsync(registryManager, configurationId); + } + catch (Exception) + { + // ignored + } + } } [Fact] @@ -729,30 +596,32 @@ public async Task GetDeploymentConfigInfoAsyncReturnsConfigWhenThereAreNoErrors( var runtime = new Mock(); var edgeAgent = new Mock(); var edgeHub = new Mock(); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; var twin = new Twin { Properties = new TwinProperties { - Desired = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 10 }, + Desired = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 10 }, - // This is here to prevent the "empty" twin error from being thrown. - { "MoreStuff", "MoreStuffHereToo" } - }).ToString()), + // This is here to prevent the "empty" twin error from being thrown. + { "MoreStuff", "MoreStuffHereToo" } + }).ToString()), Reported = new TwinCollection() } }; var deploymentConfig = new DeploymentConfig( - "1.0", runtime.Object, + "1.0", + runtime.Object, new SystemModules(edgeAgent.Object, edgeHub.Object), - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) .ReturnsAsync(deviceClient.Object); deviceClient.Setup(d => d.GetTwinAsync()) @@ -779,25 +648,27 @@ public async Task GetDeploymentConfigInfoAsyncIncludesExceptionWhenDeserializeTh // Arrange var deviceClient = new Mock(); var serde = new Mock>(); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; var twin = new Twin { Properties = new TwinProperties { - Desired = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 10 }, + Desired = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 10 }, - // This is here to prevent the "empty" twin error from being thrown. - { "MoreStuff", "MoreStuffHereToo" } - }).ToString()), + // This is here to prevent the "empty" twin error from being thrown. + { "MoreStuff", "MoreStuffHereToo" } + }).ToString()), Reported = new TwinCollection() } }; var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) .ReturnsAsync(deviceClient.Object); deviceClient.Setup(d => d.GetTwinAsync()) @@ -809,7 +680,7 @@ public async Task GetDeploymentConfigInfoAsyncIncludesExceptionWhenDeserializeTh // Act var connection = new EdgeAgentConnection(deviceClientProvider.Object, serde.Object); Assert.NotNull(connectionStatusChangesHandler); - connectionStatusChangesHandler.Invoke(Client.ConnectionStatus.Connected, Client.ConnectionStatusChangeReason.Connection_Ok); + connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok); Option deploymentConfigInfo = await connection.GetDeploymentConfigInfoAsync(); @@ -827,22 +698,24 @@ public async Task GetDeploymentConfigInfoAsyncIncludesExceptionWhenDeserializeTh var deviceClient = new Mock(); var serde = new Mock>(); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; var twin = new Twin { Properties = new TwinProperties { - Desired = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 10 } - }).ToString()), + Desired = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 10 } + }).ToString()), Reported = new TwinCollection() } }; var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) .ReturnsAsync(deviceClient.Object); deviceClient.Setup(d => d.GetTwinAsync()) @@ -851,7 +724,7 @@ public async Task GetDeploymentConfigInfoAsyncIncludesExceptionWhenDeserializeTh // Act var connection = new EdgeAgentConnection(deviceClientProvider.Object, serde.Object); Assert.NotNull(connectionStatusChangesHandler); - connectionStatusChangesHandler.Invoke(Client.ConnectionStatus.Connected, Client.ConnectionStatusChangeReason.Connection_Ok); + connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok); Option deploymentConfigInfo = await connection.GetDeploymentConfigInfoAsync(); @@ -871,37 +744,39 @@ public async Task GetDeploymentConfigInfoIncludesExceptionWhenSchemaVersionDoesN var runtime = new Mock(); var edgeAgent = new Mock(); var edgeHub = new Mock(); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; var twin = new Twin { Properties = new TwinProperties { - Desired = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 10 }, + Desired = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 10 }, - // This is here to prevent the "empty" twin error from being thrown. - { "MoreStuff", "MoreStuffHereToo" } - }).ToString()), + // This is here to prevent the "empty" twin error from being thrown. + { "MoreStuff", "MoreStuffHereToo" } + }).ToString()), Reported = new TwinCollection() } }; var deploymentConfig = new DeploymentConfig( - "InvalidSchemaVersion", runtime.Object, + "InvalidSchemaVersion", + runtime.Object, new SystemModules(edgeAgent.Object, edgeHub.Object), - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) .ReturnsAsync(deviceClient.Object); deviceClient.Setup(d => d.GetTwinAsync()) .ReturnsAsync(twin); - deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) + deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) .Returns(Task.CompletedTask); - deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) + deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); serde.Setup(s => s.Deserialize(It.IsAny())) @@ -910,7 +785,7 @@ public async Task GetDeploymentConfigInfoIncludesExceptionWhenSchemaVersionDoesN // Act var connection = new EdgeAgentConnection(deviceClientProvider.Object, serde.Object); Assert.NotNull(connectionStatusChangesHandler); - connectionStatusChangesHandler.Invoke(Client.ConnectionStatus.Connected, Client.ConnectionStatusChangeReason.Connection_Ok); + connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok); Option deploymentConfigInfo = await connection.GetDeploymentConfigInfoAsync(); // Assert @@ -930,43 +805,43 @@ public async Task GetDeploymentConfigInfoAsyncIncludesExceptionWhenGetTwinThrows var edgeAgent = new Mock(); var edgeHub = new Mock(); var retryStrategy = new Mock(new object[] { false }); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; var deploymentConfig = new DeploymentConfig( - "1.0", runtime.Object, + "1.0", + runtime.Object, new SystemModules(edgeAgent.Object, edgeHub.Object), - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) .ReturnsAsync(deviceClient.Object); deviceClient.Setup(d => d.GetTwinAsync()) .ThrowsAsync(new InvalidOperationException()); - deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) + deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) .Returns(Task.CompletedTask); - deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) + deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); serde.Setup(s => s.Deserialize(It.IsAny())) .Returns(deploymentConfig); retryStrategy.Setup(rs => rs.GetShouldRetry()) - .Returns((int retryCount, Exception lastException, out TimeSpan delay) => - { - delay = TimeSpan.Zero; - return false; - }); + .Returns( + (int retryCount, Exception lastException, out TimeSpan delay) => + { + delay = TimeSpan.Zero; + return false; + }); // Act IEdgeAgentConnection connection = new EdgeAgentConnection(deviceClientProvider.Object, serde.Object, retryStrategy.Object, TimeSpan.FromHours(1)); Assert.NotNull(connectionStatusChangesHandler); connectionStatusChangesHandler.Invoke( - Client.ConnectionStatus.Connected, - Client.ConnectionStatusChangeReason.Connection_Ok - ); + ConnectionStatus.Connected, + ConnectionStatusChangeReason.Connection_Ok); Option deploymentConfigInfo = await connection.GetDeploymentConfigInfoAsync(); // Assert @@ -986,49 +861,52 @@ public async Task GetDeploymentConfigInfoAsyncRetriesWhenGetTwinThrows() var edgeAgent = new Mock(); var edgeHub = new Mock(); var retryStrategy = new Mock(new object[] { false }); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; var deploymentConfig = new DeploymentConfig( - "1.0", runtime.Object, + "1.0", + runtime.Object, new SystemModules(edgeAgent.Object, edgeHub.Object), - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>((statusChanges, x) => connectionStatusChangesHandler = statusChanges) .ReturnsAsync(deviceClient.Object); serde.Setup(s => s.Deserialize(It.IsAny())) .Returns(deploymentConfig); retryStrategy.SetupSequence(rs => rs.GetShouldRetry()) - .Returns((int retryCount, Exception lastException, out TimeSpan delay) => - { - delay = TimeSpan.Zero; - return true; - }); + .Returns( + (int retryCount, Exception lastException, out TimeSpan delay) => + { + delay = TimeSpan.Zero; + return true; + }); var twin = new Twin { Properties = new TwinProperties { - Desired = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 10 }, + Desired = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 10 }, - // This is here to prevent the "empty" twin error from being thrown. - { "MoreStuff", "MoreStuffHereToo" } - }).ToString()), + // This is here to prevent the "empty" twin error from being thrown. + { "MoreStuff", "MoreStuffHereToo" } + }).ToString()), Reported = new TwinCollection() } }; deviceClient.SetupSequence(d => d.GetTwinAsync()) .ThrowsAsync(new InvalidOperationException()) .ReturnsAsync(twin); - deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) + deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) .Returns(Task.CompletedTask); - deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) + deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act @@ -1052,49 +930,51 @@ public async Task GetDeploymentConfigInfoAsyncReturnsConfigWhenThereAreNoErrorsW var runtime = new Mock(); var edgeAgent = new Mock(); var edgeHub = new Mock(); - Client.ConnectionStatusChangesHandler connectionStatusChangesHandler = null; - Client.DesiredPropertyUpdateCallback desiredPropertyUpdateCallback = null; + ConnectionStatusChangesHandler connectionStatusChangesHandler = null; + DesiredPropertyUpdateCallback desiredPropertyUpdateCallback = null; Func initializeCallback = null; var twin = new Twin { Properties = new TwinProperties { - Desired = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 10 }, + Desired = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 10 }, - // This is here to prevent the "empty" twin error from being thrown. - { "MoreStuff", "MoreStuffHereToo" } - }).ToString()), + // This is here to prevent the "empty" twin error from being thrown. + { "MoreStuff", "MoreStuffHereToo" } + }).ToString()), Reported = new TwinCollection() } }; var deploymentConfig = new DeploymentConfig( - "1.0", runtime.Object, + "1.0", + runtime.Object, new SystemModules(edgeAgent.Object, edgeHub.Object), - ImmutableDictionary.Empty - ); + ImmutableDictionary.Empty); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) - .Callback>( - (statusChanges, callback) => - { - connectionStatusChangesHandler = statusChanges; - initializeCallback = callback; - }) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny>())) + .Callback>( + (statusChanges, callback) => + { + connectionStatusChangesHandler = statusChanges; + initializeCallback = callback; + }) .ReturnsAsync(deviceClient.Object); deviceClient.Setup(d => d.GetTwinAsync()) - .ReturnsAsync(twin); - deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) - .Callback(p => desiredPropertyUpdateCallback = p) - .Returns(Task.CompletedTask); - deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask); + .ReturnsAsync(twin); + deviceClient.Setup(d => d.SetDesiredPropertyUpdateCallbackAsync(It.IsAny())) + .Callback(p => desiredPropertyUpdateCallback = p) + .Returns(Task.CompletedTask); + deviceClient.Setup(d => d.SetMethodHandlerAsync(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); serde.Setup(s => s.Deserialize(It.IsAny())) - .Returns(deploymentConfig); + .Returns(deploymentConfig); var connection = new EdgeAgentConnection(deviceClientProvider.Object, serde.Object); @@ -1102,17 +982,19 @@ public async Task GetDeploymentConfigInfoAsyncReturnsConfigWhenThereAreNoErrorsW // this will cause the initial desired props to get set in the connection object Assert.NotNull(connectionStatusChangesHandler); - connectionStatusChangesHandler.Invoke(Client.ConnectionStatus.Connected, Client.ConnectionStatusChangeReason.Connection_Ok); + connectionStatusChangesHandler.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok); Assert.NotNull(initializeCallback); await initializeCallback(deviceClient.Object); // Act // now send a patch update - var patch = new TwinCollection(JObject.FromObject(new Dictionary - { - { "$version", 11 } - }).ToString()); + var patch = new TwinCollection( + JObject.FromObject( + new Dictionary + { + { "$version", 11 } + }).ToString()); await desiredPropertyUpdateCallback.Invoke(patch, null); Option deploymentConfigInfo = await connection.GetDeploymentConfigInfoAsync(); @@ -1268,7 +1150,7 @@ public async Task EdgeAgentPingMethodTest() }; ISerde serde = new TypeSpecificSerDe(deserializerTypes); - Devices.ServiceClient serviceClient = Devices.ServiceClient.CreateFromConnectionString(iotHubConnectionString); + ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(iotHubConnectionString); // Assert await Assert.ThrowsAsync(() => serviceClient.InvokeDeviceMethodAsync(edgeDeviceId, Constants.EdgeAgentModuleIdentityName, new CloudToDeviceMethod("ping"))); @@ -1338,9 +1220,14 @@ public async Task EdgeAgentConnectionRefreshTest() ISerde serde = new TypeSpecificSerDe(deserializerTypes); var runtimeInfo = new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.0", null)); - var edgeAgentDockerModule = new EdgeAgentDockerModule("docker", new DockerConfig("image", ""), null, null); - var edgeHubDockerModule = new EdgeHubDockerModule("docker", ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig("image", ""), null, null); + var edgeAgentDockerModule = new EdgeAgentDockerModule("docker", new DockerConfig("image", string.Empty), null, null); + var edgeHubDockerModule = new EdgeHubDockerModule( + "docker", + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig("image", string.Empty), + null, + null); var deploymentConfig = new DeploymentConfig( "1.0", runtimeInfo, @@ -1403,9 +1290,14 @@ public async Task EdgeAgentConnectionRefreshTest_NoRefresh() ISerde serde = new TypeSpecificSerDe(deserializerTypes); var runtimeInfo = new DockerRuntimeInfo("docker", new DockerRuntimeConfig("1.0", null)); - var edgeAgentDockerModule = new EdgeAgentDockerModule("docker", new DockerConfig("image", ""), null, null); - var edgeHubDockerModule = new EdgeHubDockerModule("docker", ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig("image", ""), null, null); + var edgeAgentDockerModule = new EdgeAgentDockerModule("docker", new DockerConfig("image", string.Empty), null, null); + var edgeHubDockerModule = new EdgeHubDockerModule( + "docker", + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig("image", string.Empty), + null, + null); var deploymentConfig = new DeploymentConfig( "1.0", runtimeInfo, @@ -1476,5 +1368,137 @@ public void SchemaVersionCheckTest(string schemaVersion, Type expectedException) EdgeAgentConnection.ValidateSchemaVersion(schemaVersion); } } + + static void ValidateModules(DeploymentConfig deploymentConfig) + { + Assert.True(deploymentConfig.SystemModules.EdgeAgent.HasValue); + Assert.True(deploymentConfig.SystemModules.EdgeHub.HasValue); + + var edgeAgent = deploymentConfig.SystemModules.EdgeAgent.OrDefault() as EdgeAgentDockerModule; + Assert.NotNull(edgeAgent); + Assert.Equal(edgeAgent.Env["e1"].Value, "e1val"); + Assert.Equal(edgeAgent.Env["e2"].Value, "e2val"); + + var edgeHub = deploymentConfig.SystemModules.EdgeHub.OrDefault() as EdgeHubDockerModule; + Assert.NotNull(edgeHub); + Assert.Equal(edgeHub.Env["e3"].Value, "e3val"); + Assert.Equal(edgeHub.Env["e4"].Value, "e4val"); + + var module1 = deploymentConfig.Modules["mongoserver"] as DockerDesiredModule; + Assert.NotNull(module1); + Assert.Equal(module1.Env["e5"].Value, "e5val"); + Assert.Equal(module1.Env["e6"].Value, "e6val"); + } + + static void ValidateRuntimeConfig(IRuntimeInfo deploymentConfigRuntime) + { + var dockerRuntimeConfig = deploymentConfigRuntime as IRuntimeInfo; + Assert.NotNull(dockerRuntimeConfig); + + Assert.Null(dockerRuntimeConfig.Config.LoggingOptions); + Assert.Equal(2, dockerRuntimeConfig.Config.RegistryCredentials.Count); + RegistryCredentials r1 = dockerRuntimeConfig.Config.RegistryCredentials["r1"]; + Assert.Equal("acr1.azure.net", r1.Address); + Assert.Equal("u1", r1.Username); + Assert.Equal("p1", r1.Password); + + RegistryCredentials r2 = dockerRuntimeConfig.Config.RegistryCredentials["r2"]; + Assert.Equal("acr2.azure.net", r2.Address); + Assert.Equal("u2", r2.Username); + Assert.Equal("p2", r2.Password); + } + + static object GetEdgeAgentConfiguration() + { + var desiredProperties = new + { + schemaVersion = "1.0", + runtime = new + { + type = "docker", + settings = new + { + loggingOptions = string.Empty + } + }, + systemModules = new + { + edgeAgent = new + { + type = "docker", + settings = new + { + image = "edgeAgent", + createOptions = string.Empty + } + }, + edgeHub = new + { + type = "docker", + status = "running", + restartPolicy = "always", + settings = new + { + image = "edgeHub", + createOptions = string.Empty + } + } + }, + modules = new + { + mongoserver = new + { + version = "1.0", + type = "docker", + status = "running", + restartPolicy = "on-failure", + settings = new + { + image = "mongo", + createOptions = string.Empty + } + }, + asa = new + { + version = "1.0", + type = "docker", + status = "running", + restartPolicy = "on-failure", + settings = new + { + image = "asa", + createOptions = string.Empty + } + } + } + }; + return desiredProperties; + } + + static object GetEdgeHubConfiguration() + { + var desiredProperties = new + { + schemaVersion = "1.0", + routes = new Dictionary + { + ["route1"] = "from /* INTO $upstream", + }, + storeAndForwardConfiguration = new + { + timeToLiveSecs = 20 + } + }; + return desiredProperties; + } + + static object GetTwinConfiguration(string moduleName) + { + var desiredProperties = new + { + name = moduleName + }; + return desiredProperties; + } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test.csproj b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test.csproj index d840cc2d562..5dd25c8aa0b 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test.csproj +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test.csproj @@ -35,4 +35,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleClientTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleClientTest.cs index d84fc47c695..45a14f05226 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleClientTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleClientTest.cs @@ -4,25 +4,27 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test using System; using System.Collections.Generic; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Client; using Microsoft.Azure.Devices.Edge.Agent.Core; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; using Xunit; + using ModuleClient = Microsoft.Azure.Devices.Edge.Agent.IoTHub.ModuleClient; public class ModuleClientTest { [Theory] - [InlineData(UpstreamProtocol.AmqpWs, Client.TransportType.Amqp_WebSocket_Only)] - [InlineData(UpstreamProtocol.Amqp, Client.TransportType.Amqp_Tcp_Only)] - [InlineData(UpstreamProtocol.MqttWs, Client.TransportType.Mqtt_WebSocket_Only)] - [InlineData(UpstreamProtocol.Mqtt, Client.TransportType.Mqtt_Tcp_Only)] + [InlineData(UpstreamProtocol.AmqpWs, TransportType.Amqp_WebSocket_Only)] + [InlineData(UpstreamProtocol.Amqp, TransportType.Amqp_Tcp_Only)] + [InlineData(UpstreamProtocol.MqttWs, TransportType.Mqtt_WebSocket_Only)] + [InlineData(UpstreamProtocol.Mqtt, TransportType.Mqtt_Tcp_Only)] [Unit] - public async Task CreateForUpstreamProtocolTest(UpstreamProtocol upstreamProtocol, Client.TransportType expectedTransportType) + public async Task CreateForUpstreamProtocolTest(UpstreamProtocol upstreamProtocol, TransportType expectedTransportType) { // Arrange - var receivedTransportType = Client.TransportType.Http1; - Task ModuleClientCreator(Client.TransportType transportType) + var receivedTransportType = TransportType.Http1; + + Task ModuleClientCreator(TransportType transportType) { receivedTransportType = transportType; return Task.FromResult((Client.ModuleClient)null); @@ -40,14 +42,17 @@ public async Task CreateForUpstreamProtocolTest(UpstreamProtocol upstreamProtoco public async Task CreateForNoUpstreamProtocolTest() { // Arrange - var receivedTransportTypes = new List(); - Task DeviceClientCreator(Client.TransportType transportType) + var receivedTransportTypes = new List(); + + Task DeviceClientCreator(TransportType transportType) { receivedTransportTypes.Add(transportType); return receivedTransportTypes.Count == 1 ? Task.FromException(new InvalidOperationException()) - : Task.FromResult(Client.ModuleClient.Create("example.com", - new Client.ModuleAuthenticationWithToken("deviceid", "moduleid", TokenHelper.CreateSasToken("foo.azure-devices.net")))); + : Task.FromResult( + Client.ModuleClient.Create( + "example.com", + new ModuleAuthenticationWithToken("deviceid", "moduleid", TokenHelper.CreateSasToken("foo.azure-devices.net")))); } // Act @@ -55,8 +60,8 @@ public async Task CreateForNoUpstreamProtocolTest() // Assert Assert.Equal(2, receivedTransportTypes.Count); - Assert.Equal(Client.TransportType.Amqp_Tcp_Only, receivedTransportTypes[0]); - Assert.Equal(Client.TransportType.Amqp_WebSocket_Only, receivedTransportTypes[1]); + Assert.Equal(TransportType.Amqp_Tcp_Only, receivedTransportTypes[0]); + Assert.Equal(TransportType.Amqp_WebSocket_Only, receivedTransportTypes[1]); } } } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleIdentityLifecycleManagerTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleIdentityLifecycleManagerTest.cs index 53f4872211f..c51e263ca94 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleIdentityLifecycleManagerTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/ModuleIdentityLifecycleManagerTest.cs @@ -27,7 +27,7 @@ public async Task TestGetModulesIdentity_WithEmptyDiff_ShouldReturnEmptyIdentiti string hostname = "hostname"; string deviceId = "deviceId"; string gatewayHostName = "localhost"; - + IImmutableDictionary modulesIdentities = await new ModuleIdentityLifecycleManager(serviceClient.Object, hostname, deviceId, gatewayHostName) .GetModuleIdentitiesAsync(ModuleSet.Empty, ModuleSet.Empty); @@ -52,13 +52,12 @@ public async Task TestGetModulesIdentity_WithUpdatedModules_NoServiceIdentity_Sh createdModuleIdentity.Authentication = new AuthenticationMechanism(); createdModuleIdentity.Authentication.Type = AuthenticationType.Sas; createdModuleIdentity.Authentication.SymmetricKey.PrimaryKey = moduleSharedAccessKey; - Module[] updatedServiceIdentities = { createdModuleIdentity}; + Module[] updatedServiceIdentities = { createdModuleIdentity }; // If we change to IList Mock doesn't recognize and making it a non Lambda would add a lot of complexity on this code. // ReSharper disable PossibleMultipleEnumeration serviceClient.Setup(sc => sc.CreateModules(It.Is>(m => m.Count() == 1 && m.First() == Name))).Returns(Task.FromResult(updatedServiceIdentities)); // ReSharper restore PossibleMultipleEnumeration - var module = new TestModule(Name, "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); IImmutableDictionary modulesIdentities = await new ModuleIdentityLifecycleManager(serviceClient.Object, hostname, deviceId, gatewayHostName) @@ -211,7 +210,7 @@ public async Task TestGetModulesIdentity_WithUpdatedModules_SymmKeyNull_ShouldUp string deviceId = "deviceId"; string sharedAccessKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("primaryAccessKey")); string gatewayHostName = "localhost"; - + Module[] serviceIdentities = { serviceModuleIdentity }; serviceClient.Setup(sc => sc.GetModules()).Returns(Task.FromResult(serviceIdentities.AsEnumerable())); serviceClient.Setup(sc => sc.UpdateModules(It.IsAny>())).Callback( @@ -286,14 +285,13 @@ public async Task TestGetModulesIdentity_WithRemovedModules_ShouldRemove() string deviceId = "deviceId"; string gatewayHostName = "localhost"; - var serviceIdentities = new List(); + var serviceIdentities = new List(); serviceIdentities.Add(serviceModuleIdentity); serviceClient.Setup(sc => sc.GetModules()).Returns(Task.FromResult(serviceIdentities.AsEnumerable())); // If we change to IList Mock doesn't recognize and making it a non Lambda would add a lot of complexity on this code. // ReSharper disable PossibleMultipleEnumeration serviceClient.Setup(sc => sc.RemoveModules(It.Is>(m => m.Count() == 1 && m.First() == Name))).Returns(Task.FromResult(ImmutableList.Empty.AsEnumerable())); // ReSharper restore PossibleMultipleEnumeration - await new ModuleIdentityLifecycleManager(serviceClient.Object, hostname, deviceId, gatewayHostName) .GetModuleIdentitiesAsync(ModuleSet.Empty, ModuleSet.Create(new IModule[] { currentModule })); @@ -318,7 +316,7 @@ public async Task TestGetModulesIdentity_WithRemovedModules_NotEdgeHubManaged_Sh string deviceId = "deviceId"; string gatewayHostName = "localhost"; - var serviceIdentities = new List(); + var serviceIdentities = new List(); serviceIdentities.Add(serviceModuleIdentity); serviceClient.Setup(sc => sc.GetModules()).Returns(Task.FromResult(serviceIdentities.AsEnumerable())); serviceClient.Setup(sc => sc.RemoveModules(It.IsAny>())).Returns(Task.FromResult(ImmutableList.Empty.AsEnumerable())); @@ -405,12 +403,11 @@ public async Task TestGetModuleIdentities_WhenOffline_ReturnsEmptyList() // ReSharper disable PossibleMultipleEnumeration serviceClient.Setup(sc => sc.CreateModules(It.Is>(m => m.Count() == 1 && m.First() == Name))).ThrowsAsync(new InvalidOperationException()); // ReSharper restore PossibleMultipleEnumeration - var module = new TestModule(Name, "v1", "test", ModuleStatus.Running, new TestConfig("image"), RestartPolicy.OnUnhealthy, DefaultConfigurationInfo, EnvVars); IImmutableDictionary modulesIdentities = await new ModuleIdentityLifecycleManager(serviceClient.Object, hostname, deviceId, gatewayHostName) .GetModuleIdentitiesAsync(ModuleSet.Create(module), ModuleSet.Empty); - + serviceClient.VerifyAll(); Assert.False(modulesIdentities.Any()); } diff --git a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/reporters/IoTHubReporterTest.cs b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/reporters/IoTHubReporterTest.cs index 78fe6020b3a..9ca7cca7afa 100644 --- a/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/reporters/IoTHubReporterTest.cs +++ b/edge-agent/test/Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test/reporters/IoTHubReporterTest.cs @@ -19,6 +19,7 @@ namespace Microsoft.Azure.Devices.Edge.Agent.IoTHub.Test.Reporters using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; + using ConfigurationInfo = Microsoft.Azure.Devices.Edge.Agent.Core.ConfigurationInfo; public class IoTHubReporterTest { @@ -61,11 +62,6 @@ public async void SkipReportIfNoSavedStateAndNoStateFromConfigSource() } } - IEdgeAgentModule CreateMockEdgeAgentModule() => new TestAgentModule( - Constants.EdgeAgentModuleName, "docker", - new TestConfig("EdgeAgentImage"), new Core.ConfigurationInfo(), new Dictionary() - ); - [Fact] [Unit] public async void ClearAndGenerateNewReportedInfoIfDeserializeFails() @@ -85,26 +81,42 @@ public async void ClearAndGenerateNewReportedInfoIfDeserializeFails() var versionInfo = new VersionInfo("v1", "b1", "c1"); // Mock IEdgeAgentConnection var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "extra_mod", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), + "extra_mod", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), string.Empty, - versionInfo - ); + versionInfo); edgeAgentConnection .SetupGet(c => c.ReportedProperties) .Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); @@ -122,13 +134,12 @@ public async void ClearAndGenerateNewReportedInfoIfDeserializeFails() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( - RuntimeType, - (deploymentConfigInfo.DeploymentConfig.Runtime as DockerRuntimeInfo)?.Config, - new DockerPlatformInfo(OperatingSystemType, Architecture, Version)); + RuntimeType, + (deploymentConfigInfo.DeploymentConfig.Runtime as DockerRuntimeInfo)?.Config, + new DockerPlatformInfo(OperatingSystemType, Architecture, Version)); // Mock AgentStateSerDe var agentStateSerde = new Mock>(); @@ -139,23 +150,39 @@ public async void ClearAndGenerateNewReportedInfoIfDeserializeFails() ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); - - //Act + // Act var reporter = new IoTHubReporter(edgeAgentConnection.Object, agentStateSerde.Object, versionInfo); await reporter.ReportAsync(cts.Token, currentModuleSet, runtimeInfo, DesiredVersion, Option.Some(DeploymentStatus.Success)); - //Assert + // Assert Assert.Equal(2, patches.Count); JObject patch1Json = JObject.Parse(patches[0].ToJson()); foreach (KeyValuePair keyValuePair in patch1Json) @@ -164,53 +191,54 @@ public async void ClearAndGenerateNewReportedInfoIfDeserializeFails() } JObject patch2Json = JObject.Parse(patches[1].ToJson()); - JObject expectedPatch2Json = JObject.FromObject(new - { - schemaVersion = SchemaVersion, - version = new - { - version = versionInfo.Version, - build = versionInfo.Build, - commit = versionInfo.Commit - }, - lastDesiredVersion = DesiredVersion, - lastDesiredStatus = new + JObject expectedPatch2Json = JObject.FromObject( + new { - code = (int)DeploymentStatusCode.Successful - }, - runtime = new - { - type = RuntimeType, - settings = new + schemaVersion = SchemaVersion, + version = new { - minDockerVersion = MinDockerVersion, - loggingOptions = LoggingOptions, - registryCredentials = new { } + version = versionInfo.Version, + build = versionInfo.Build, + commit = versionInfo.Commit }, - platform = new + lastDesiredVersion = DesiredVersion, + lastDesiredStatus = new { - os = OperatingSystemType, - architecture = Architecture, - version = Version - } - }, - systemModules = new - { - edgeAgent = new + code = (int)DeploymentStatusCode.Successful + }, + runtime = new { - type = "docker", + type = RuntimeType, settings = new { - image = "EdgeAgentImage" + minDockerVersion = MinDockerVersion, + loggingOptions = LoggingOptions, + registryCredentials = new { } + }, + platform = new + { + os = OperatingSystemType, + architecture = Architecture, + version = Version + } + }, + systemModules = new + { + edgeAgent = new + { + type = "docker", + settings = new + { + image = "EdgeAgentImage" + } } + }, + modules = new Dictionary + { + { currentModuleSet.Modules["mod1"].Name, currentModuleSet.Modules["mod1"] }, + { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, } - }, - modules = new Dictionary - { - { currentModuleSet.Modules["mod1"].Name, currentModuleSet.Modules["mod1"] }, - { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, - } - }); + }); Assert.True(JToken.DeepEquals(expectedPatch2Json, patch2Json)); } } @@ -234,26 +262,42 @@ public async void ReportedPatchTest() // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "extra_mod", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), + "extra_mod", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), string.Empty, - versionInfo - ); + versionInfo); edgeAgentConnection .SetupGet(c => c.ReportedProperties) .Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); @@ -271,8 +315,7 @@ public async void ReportedPatchTest() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( RuntimeType, @@ -284,16 +327,33 @@ public async void ReportedPatchTest() ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -307,54 +367,55 @@ public async void ReportedPatchTest() Assert.NotNull(patch); JObject patchJson = JObject.Parse(patch.ToJson()); - JObject expectedPatchJson = JObject.FromObject(new - { - schemaVersion = SchemaVersion, - lastDesiredVersion = DesiredVersion, - lastDesiredStatus = new + JObject expectedPatchJson = JObject.FromObject( + new { - code = (int)DeploymentStatusCode.Successful - }, - runtime = new - { - type = RuntimeType, - settings = new + schemaVersion = SchemaVersion, + lastDesiredVersion = DesiredVersion, + lastDesiredStatus = new { - minDockerVersion = MinDockerVersion, - loggingOptions = LoggingOptions, - registryCredentials = new { } + code = (int)DeploymentStatusCode.Successful }, - platform = new + runtime = new { - os = OperatingSystemType, - architecture = Architecture, - version = Version - } - }, - systemModules = new - { - edgeAgent = new - { - type = "docker", + type = RuntimeType, settings = new { - image = "EdgeAgentImage" + minDockerVersion = MinDockerVersion, + loggingOptions = LoggingOptions, + registryCredentials = new { } + }, + platform = new + { + os = OperatingSystemType, + architecture = Architecture, + version = Version } - } - }, - modules = new Dictionary - { + }, + systemModules = new { - currentModuleSet.Modules["mod1"].Name, - new + edgeAgent = new { - runtimeStatus = "backoff" + type = "docker", + settings = new + { + image = "EdgeAgentImage" + } } }, - { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, - { "extra_mod", null } - } - }); + modules = new Dictionary + { + { + currentModuleSet.Modules["mod1"].Name, + new + { + runtimeStatus = "backoff" + } + }, + { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, + { "extra_mod", null } + } + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } @@ -379,26 +440,42 @@ public async void ReportedPatchNoneStatusTest() // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "extra_mod", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), + "extra_mod", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), string.Empty, - versionInfo - ); + versionInfo); edgeAgentConnection .SetupGet(c => c.ReportedProperties) .Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); @@ -416,8 +493,7 @@ public async void ReportedPatchNoneStatusTest() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( RuntimeType, @@ -429,16 +505,33 @@ public async void ReportedPatchNoneStatusTest() ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -452,54 +545,55 @@ public async void ReportedPatchNoneStatusTest() Assert.NotNull(patch); JObject patchJson = JObject.Parse(patch.ToJson()); - JObject expectedPatchJson = JObject.FromObject(new - { - schemaVersion = SchemaVersion, - lastDesiredVersion = DesiredVersion, - lastDesiredStatus = new - { - code = (int)DeploymentStatusCode.Successful - }, - runtime = new + JObject expectedPatchJson = JObject.FromObject( + new { - type = RuntimeType, - settings = new + schemaVersion = SchemaVersion, + lastDesiredVersion = DesiredVersion, + lastDesiredStatus = new { - minDockerVersion = MinDockerVersion, - loggingOptions = LoggingOptions, - registryCredentials = new { } + code = (int)DeploymentStatusCode.Successful }, - platform = new - { - os = OperatingSystemType, - architecture = Architecture, - version = Version - } - }, - systemModules = new - { - edgeAgent = new + runtime = new { - type = "docker", + type = RuntimeType, settings = new { - image = "EdgeAgentImage" + minDockerVersion = MinDockerVersion, + loggingOptions = LoggingOptions, + registryCredentials = new { } + }, + platform = new + { + os = OperatingSystemType, + architecture = Architecture, + version = Version } - } - }, - modules = new Dictionary - { + }, + systemModules = new { - currentModuleSet.Modules["mod1"].Name, - new + edgeAgent = new { - runtimeStatus = "backoff" + type = "docker", + settings = new + { + image = "EdgeAgentImage" + } } }, - { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, - { "extra_mod", null } - } - }); + modules = new Dictionary + { + { + currentModuleSet.Modules["mod1"].Name, + new + { + runtimeStatus = "backoff" + } + }, + { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, + { "extra_mod", null } + } + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); @@ -533,33 +627,51 @@ public async void ReportedPatchTestStripMetadata() // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "extra_mod", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), - string.Empty, versionInfo - ); - - edgeAgentConnection.SetupGet(c => c.ReportedProperties).Returns(() => - { - var coll = new TwinCollection(JsonConvert.SerializeObject(reportedState)); - coll["$metadata"] = JObject.FromObject(new { foo = 10 }); - coll["$version"] = 42; - return Option.Some(coll); - }); + "extra_mod", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), + string.Empty, + versionInfo); + + edgeAgentConnection.SetupGet(c => c.ReportedProperties).Returns( + () => + { + var coll = new TwinCollection(JsonConvert.SerializeObject(reportedState)); + coll["$metadata"] = JObject.FromObject(new { foo = 10 }); + coll["$version"] = 42; + return Option.Some(coll); + }); TwinCollection patch = null; edgeAgentConnection.Setup(c => c.UpdateReportedPropertiesAsync(It.IsAny())) @@ -574,8 +686,7 @@ public async void ReportedPatchTestStripMetadata() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IEdgeAgentModule edgeAgentModule = this.CreateMockEdgeAgentModule(); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( @@ -587,16 +698,33 @@ public async void ReportedPatchTestStripMetadata() ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -610,54 +738,55 @@ public async void ReportedPatchTestStripMetadata() Assert.NotNull(patch); var patchJson = JsonConvert.DeserializeObject(patch.ToJson()) as JObject; - JObject expectedPatchJson = JObject.FromObject(new - { - schemaVersion = SchemaVersion, - lastDesiredVersion = DesiredVersion, - lastDesiredStatus = new - { - code = (int)DeploymentStatusCode.Successful - }, - runtime = new + JObject expectedPatchJson = JObject.FromObject( + new { - type = RuntimeType, - settings = new + schemaVersion = SchemaVersion, + lastDesiredVersion = DesiredVersion, + lastDesiredStatus = new { - minDockerVersion = MinDockerVersion, - loggingOptions = LoggingOptions, - registryCredentials = new { } + code = (int)DeploymentStatusCode.Successful }, - platform = new - { - os = OperatingSystemType, - architecture = Architecture, - version = Version - } - }, - systemModules = new - { - edgeAgent = new + runtime = new { - type = "docker", + type = RuntimeType, settings = new { - image = "EdgeAgentImage" + minDockerVersion = MinDockerVersion, + loggingOptions = LoggingOptions, + registryCredentials = new { } + }, + platform = new + { + os = OperatingSystemType, + architecture = Architecture, + version = Version } - } - }, - modules = new Dictionary - { + }, + systemModules = new { - currentModuleSet.Modules["mod1"].Name, - new + edgeAgent = new { - runtimeStatus = "backoff" + type = "docker", + settings = new + { + image = "EdgeAgentImage" + } } }, - { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, - { "extra_mod", null } - } - }); + modules = new Dictionary + { + { + currentModuleSet.Modules["mod1"].Name, + new + { + runtimeStatus = "backoff" + } + }, + { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, + { "extra_mod", null } + } + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } @@ -681,25 +810,42 @@ public async void ReportedPatchTest2() // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "extra_mod", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), - string.Empty, versionInfo - ); + "extra_mod", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), + string.Empty, + versionInfo); edgeAgentConnection.SetupGet(c => c.ReportedProperties).Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); TwinCollection patch = null; @@ -715,8 +861,7 @@ public async void ReportedPatchTest2() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IEdgeAgentModule edgeAgentModule = this.CreateMockEdgeAgentModule(); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( @@ -728,16 +873,33 @@ public async void ReportedPatchTest2() ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -753,16 +915,33 @@ public async void ReportedPatchTest2() currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Failed - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Failed), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); await reporter.ReportAsync(cts.Token, currentModuleSet, runtimeInfo, DesiredVersion, Option.Some(DeploymentStatus.Success)); @@ -770,19 +949,20 @@ public async void ReportedPatchTest2() Assert.NotNull(patch); var patchJson = JsonConvert.DeserializeObject(patch.ToJson()) as JObject; - JObject expectedPatchJson = JObject.FromObject(new - { - modules = new Dictionary + JObject expectedPatchJson = JObject.FromObject( + new { + modules = new Dictionary { - currentModuleSet.Modules["mod1"].Name, - new { - runtimeStatus = "failed" + currentModuleSet.Modules["mod1"].Name, + new + { + runtimeStatus = "failed" + } } } - } - }); + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } @@ -806,25 +986,42 @@ public async void ReportAsyncDoesNotReportIfPatchIsEmpty() // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - DesiredVersion, DeploymentStatus.Success, + var reportedState = new AgentState( + DesiredVersion, + DeploymentStatus.Success, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), - string.Empty, versionInfo - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), + string.Empty, + versionInfo); edgeAgentConnection.SetupGet(c => c.ReportedProperties).Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); edgeAgentConnection.Setup(c => c.UpdateReportedPropertiesAsync(It.IsAny())) .Returns(Task.CompletedTask); @@ -837,8 +1034,7 @@ public async void ReportAsyncDoesNotReportIfPatchIsEmpty() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( RuntimeType, @@ -848,16 +1044,33 @@ public async void ReportAsyncDoesNotReportIfPatchIsEmpty() // build current module set ModuleSet currentModuleSet = ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -908,56 +1121,81 @@ public async void ReportedPatchIncludesEdgeHubInSystemModulesTest() // prepare AgentConfig var edgeHubDesiredModule = new EdgeHubDockerModule( - "docker", ModuleStatus.Running, RestartPolicy.Always, + "docker", + ModuleStatus.Running, + RestartPolicy.Always, new DockerConfig("edge.azurecr.io/edgeHub:1.0"), - new Core.ConfigurationInfo("1"), new Dictionary() - ); + new ConfigurationInfo("1"), + new Dictionary()); var edgeAgentDesiredModule = new EdgeAgentDockerModule( - "docker", new DockerConfig("edge.azurecr.io/edgeAgent:1.0"), - new Core.ConfigurationInfo("1"), new Dictionary() - ); + "docker", + new DockerConfig("edge.azurecr.io/edgeAgent:1.0"), + new ConfigurationInfo("1"), + new Dictionary()); var deploymentConfig = new DeploymentConfig( "1.0", new DockerRuntimeInfo(RuntimeType, new DockerRuntimeConfig(MinDockerVersion, LoggingOptions)), new SystemModules(edgeAgentDesiredModule, edgeHubDesiredModule), - new Dictionary() - ); + new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IEdgeAgentModule edgeAgentModule = this.CreateMockEdgeAgentModule(); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( - RuntimeType, - (deploymentConfigInfo.DeploymentConfig.Runtime as DockerRuntimeInfo)?.Config, - new DockerPlatformInfo(OperatingSystemType, Architecture, Version)); + RuntimeType, + (deploymentConfigInfo.DeploymentConfig.Runtime as DockerRuntimeInfo)?.Config, + new DockerPlatformInfo(OperatingSystemType, Architecture, Version)); // build current module set DateTime lastStartTimeUtc = DateTime.Parse( - "2017-11-13T23:44:35.127381Z", null, DateTimeStyles.RoundtripKind - ); + "2017-11-13T23:44:35.127381Z", + null, + DateTimeStyles.RoundtripKind); var edgeHubRuntimeModule = new EdgeHubDockerRuntimeModule( - ModuleStatus.Running, RestartPolicy.Always, - new DockerConfig("edge.azurecr.io/edgeHub:1.0"), 0, string.Empty, - lastStartTimeUtc, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running, - new Core.ConfigurationInfo("1"), new Dictionary { ["foo"] = new EnvVal("Bar") } - ); + ModuleStatus.Running, + RestartPolicy.Always, + new DockerConfig("edge.azurecr.io/edgeHub:1.0"), + 0, + string.Empty, + lastStartTimeUtc, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + new ConfigurationInfo("1"), + new Dictionary { ["foo"] = new EnvVal("Bar") }); ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), - edgeHubRuntimeModule - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), + edgeHubRuntimeModule); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -971,54 +1209,55 @@ public async void ReportedPatchIncludesEdgeHubInSystemModulesTest() Assert.NotNull(patch); JObject patchJson = JObject.Parse(patch.ToJson()); - JObject expectedPatchJson = JObject.FromObject(new - { - schemaVersion = SchemaVersion, - version = new - { - version = versionInfo.Version, - build = versionInfo.Build, - commit = versionInfo.Commit - }, - lastDesiredVersion = DesiredVersion, - lastDesiredStatus = new - { - code = (int)DeploymentStatusCode.Successful - }, - runtime = new + JObject expectedPatchJson = JObject.FromObject( + new { - type = RuntimeType, - settings = new + schemaVersion = SchemaVersion, + version = new { - minDockerVersion = MinDockerVersion, - loggingOptions = LoggingOptions, - registryCredentials = new { } + version = versionInfo.Version, + build = versionInfo.Build, + commit = versionInfo.Commit }, - platform = new + lastDesiredVersion = DesiredVersion, + lastDesiredStatus = new { - os = OperatingSystemType, - architecture = Architecture, - version = Version - } - }, - systemModules = new - { - edgeHub = edgeHubRuntimeModule, - edgeAgent = new + code = (int)DeploymentStatusCode.Successful + }, + runtime = new { - type = "docker", + type = RuntimeType, settings = new { - image = "EdgeAgentImage" + minDockerVersion = MinDockerVersion, + loggingOptions = LoggingOptions, + registryCredentials = new { } + }, + platform = new + { + os = OperatingSystemType, + architecture = Architecture, + version = Version } + }, + systemModules = new + { + edgeHub = edgeHubRuntimeModule, + edgeAgent = new + { + type = "docker", + settings = new + { + image = "EdgeAgentImage" + } + } + }, + modules = new + { + mod1 = currentModuleSet.Modules["mod1"], + mod2 = currentModuleSet.Modules["mod2"] } - }, - modules = new - { - mod1 = currentModuleSet.Modules["mod1"], - mod2 = currentModuleSet.Modules["mod2"] - } - }); + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } @@ -1058,20 +1297,21 @@ public async void ReportAsyncAcceptsNullInputs() Assert.NotNull(patch); JObject patchJson = JObject.Parse(patch.ToJson()); - JObject expectedPatchJson = JObject.FromObject(new - { - schemaVersion = "1.0", - lastDesiredStatus = new - { - code = (int)DeploymentStatusCode.Successful - }, - version = new + JObject expectedPatchJson = JObject.FromObject( + new { - version = versionInfo.Version, - build = versionInfo.Build, - commit = versionInfo.Commit - } - }); + schemaVersion = "1.0", + lastDesiredStatus = new + { + code = (int)DeploymentStatusCode.Successful + }, + version = new + { + version = versionInfo.Version, + build = versionInfo.Build, + commit = versionInfo.Commit + } + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } @@ -1102,7 +1342,6 @@ public async void ReportEmptyShutdown() // Assert Assert.Null(patch); } - } [Fact] @@ -1122,20 +1361,19 @@ public async void ReportShutdown() var versionInfo = new VersionInfo("v1", "b1", "c1"); DateTime lastStartTimeUtc = DateTime.Parse("2017-11-13T23:44:35.127381Z", null, DateTimeStyles.RoundtripKind); - IEdgeAgentModule edgeAgent = new EdgeAgentDockerRuntimeModule(new DockerReportedConfig("image", string.Empty, "hash"), ModuleStatus.Running, 0, string.Empty, lastStartTimeUtc, DateTime.MinValue, new Core.ConfigurationInfo("id"), new Dictionary()); - IEdgeHubModule edgeHub = new EdgeHubDockerRuntimeModule(ModuleStatus.Running, RestartPolicy.Always, new DockerReportedConfig("hubimage", string.Empty, "hash"), 0, string.Empty, DateTime.Now, DateTime.Now, 0, DateTime.Now, ModuleStatus.Running, new Core.ConfigurationInfo("hub"), new Dictionary()); + IEdgeAgentModule edgeAgent = new EdgeAgentDockerRuntimeModule(new DockerReportedConfig("image", string.Empty, "hash"), ModuleStatus.Running, 0, string.Empty, lastStartTimeUtc, DateTime.MinValue, new ConfigurationInfo("id"), new Dictionary()); + IEdgeHubModule edgeHub = new EdgeHubDockerRuntimeModule(ModuleStatus.Running, RestartPolicy.Always, new DockerReportedConfig("hubimage", string.Empty, "hash"), 0, string.Empty, DateTime.Now, DateTime.Now, 0, DateTime.Now, ModuleStatus.Running, new ConfigurationInfo("hub"), new Dictionary()); // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Empty.Modules.ToImmutableDictionary(), string.Empty, - versionInfo - ); + versionInfo); edgeAgentConnection.SetupGet(c => c.ReportedProperties).Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); TwinCollection patch = null; @@ -1151,29 +1389,45 @@ public async void ReportShutdown() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( - RuntimeType, - (deploymentConfigInfo.DeploymentConfig.Runtime as DockerRuntimeInfo)?.Config, - new DockerPlatformInfo(OperatingSystemType, Architecture, Version)); + RuntimeType, + (deploymentConfigInfo.DeploymentConfig.Runtime as DockerRuntimeInfo)?.Config, + new DockerPlatformInfo(OperatingSystemType, Architecture, Version)); // build current module set ModuleSet currentModuleSet = ModuleSet.Create( edgeAgent, edgeHub, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running)); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -1192,43 +1446,44 @@ public async void ReportShutdown() Assert.NotNull(patch); var patchJson = JsonConvert.DeserializeObject(patch.ToJson()) as JObject; - JObject expectedPatchJson = JObject.FromObject(new - { - systemModules = new Dictionary + JObject expectedPatchJson = JObject.FromObject( + new { + systemModules = new Dictionary { - edgeAgent.Name, - new - { - runtimeStatus = "unknown" - } - }, - { - edgeHub.Name, - new { - runtimeStatus = "unknown" - } - } - }, - modules = new Dictionary - { - { - currentModuleSet.Modules["mod1"].Name, - new + edgeAgent.Name, + new + { + runtimeStatus = "unknown" + } + }, { - runtimeStatus = "unknown" + edgeHub.Name, + new + { + runtimeStatus = "unknown" + } } }, + modules = new Dictionary { - currentModuleSet.Modules["mod2"].Name, - new { - runtimeStatus = "unknown" + currentModuleSet.Modules["mod1"].Name, + new + { + runtimeStatus = "unknown" + } + }, + { + currentModuleSet.Modules["mod2"].Name, + new + { + runtimeStatus = "unknown" + } } } - } - }); + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } @@ -1253,26 +1508,42 @@ public async void ReportedPatchWithEnvVarsTest() // prepare IEdgeAgentConnection mock var edgeAgentConnection = new Mock(); - var reportedState = new AgentState - ( - 0, DeploymentStatus.Unknown, + var reportedState = new AgentState( + 0, + DeploymentStatus.Unknown, null, null, ModuleSet.Create( new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running), new TestRuntimeModule( - "extra_mod", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff - ) - ).Modules.ToImmutableDictionary(), + "extra_mod", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff)).Modules.ToImmutableDictionary(), string.Empty, - versionInfo - ); + versionInfo); edgeAgentConnection .SetupGet(c => c.ReportedProperties) .Returns(Option.Some(new TwinCollection(JsonConvert.SerializeObject(reportedState)))); @@ -1295,8 +1566,7 @@ public async void ReportedPatchWithEnvVarsTest() new Dictionary()); var deploymentConfigInfo = new DeploymentConfigInfo( DesiredVersion, - deploymentConfig - ); + deploymentConfig); IRuntimeInfo runtimeInfo = new DockerReportedRuntimeInfo( RuntimeType, @@ -1308,16 +1578,37 @@ public async void ReportedPatchWithEnvVarsTest() ModuleSet currentModuleSet = ModuleSet.Create( edgeAgentModule, new TestRuntimeModule( - "mod1", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Backoff, null, new Dictionary { ["e1"] = new EnvVal("e1Val") } - ), + "mod1", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Backoff, + null, + new Dictionary { ["e1"] = new EnvVal("e1Val") }), new TestRuntimeModule( - "mod2", "1.0", RestartPolicy.OnUnhealthy, "test", ModuleStatus.Running, - new TestConfig("image1"), 0, string.Empty, DateTime.MinValue, DateTime.MinValue, - 0, DateTime.MinValue, ModuleStatus.Running, null, new Dictionary { ["e2"] = new EnvVal("e2Val") } - ) - ); + "mod2", + "1.0", + RestartPolicy.OnUnhealthy, + "test", + ModuleStatus.Running, + new TestConfig("image1"), + 0, + string.Empty, + DateTime.MinValue, + DateTime.MinValue, + 0, + DateTime.MinValue, + ModuleStatus.Running, + null, + new Dictionary { ["e2"] = new EnvVal("e2Val") })); var agentStateSerde = new Mock>(); agentStateSerde.Setup(s => s.Deserialize(It.IsAny())) @@ -1331,78 +1622,86 @@ public async void ReportedPatchWithEnvVarsTest() Assert.NotNull(patch); JObject patchJson = JObject.Parse(patch.ToJson()); - JObject expectedPatchJson = JObject.FromObject(new - { - schemaVersion = SchemaVersion, - lastDesiredVersion = DesiredVersion, - lastDesiredStatus = new - { - code = (int)DeploymentStatusCode.Successful - }, - runtime = new + JObject expectedPatchJson = JObject.FromObject( + new { - type = RuntimeType, - settings = new + schemaVersion = SchemaVersion, + lastDesiredVersion = DesiredVersion, + lastDesiredStatus = new { - minDockerVersion = MinDockerVersion, - loggingOptions = LoggingOptions, - registryCredentials = new + code = (int)DeploymentStatusCode.Successful + }, + runtime = new + { + type = RuntimeType, + settings = new { - r1 = new + minDockerVersion = MinDockerVersion, + loggingOptions = LoggingOptions, + registryCredentials = new { - address = "a1", - username = "u1", - password = "p1" - }, - r2 = new - { - address = "a2", - username = "u2", - password = "p2" + r1 = new + { + address = "a1", + username = "u1", + password = "p1" + }, + r2 = new + { + address = "a2", + username = "u2", + password = "p2" + } } + }, + platform = new + { + os = OperatingSystemType, + architecture = Architecture, + version = Version } }, - platform = new + systemModules = new { - os = OperatingSystemType, - architecture = Architecture, - version = Version - } - }, - systemModules = new - { - edgeAgent = new - { - type = "docker", - settings = new + edgeAgent = new { - image = "EdgeAgentImage" + type = "docker", + settings = new + { + image = "EdgeAgentImage" + } } - } - }, - modules = new Dictionary - { + }, + modules = new Dictionary { - currentModuleSet.Modules["mod1"].Name, - new { - runtimeStatus = "backoff", - env = new + currentModuleSet.Modules["mod1"].Name, + new { - e1 = new + runtimeStatus = "backoff", + env = new { - value = "e1Val" + e1 = new + { + value = "e1Val" + } } } - } - }, - { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, - { "extra_mod", null } - } - }); + }, + { currentModuleSet.Modules["mod2"].Name, currentModuleSet.Modules["mod2"] }, + { "extra_mod", null } + } + }); Assert.True(JToken.DeepEquals(expectedPatchJson, patchJson)); } } + + IEdgeAgentModule CreateMockEdgeAgentModule() => new TestAgentModule( + Constants.EdgeAgentModuleName, + "docker", + new TestConfig("EdgeAgentImage"), + new ConfigurationInfo(), + new Dictionary()); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpConnectionUtils.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpConnectionUtils.cs index 6ab3bd1e7b4..0ee56e76351 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpConnectionUtils.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpConnectionUtils.cs @@ -25,6 +25,7 @@ public static string GetCorrelationId(IAmqpLink link) return correlationId; } } + return string.Empty; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpEventIds.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpEventIds.cs index 228c0385555..848b714f827 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpEventIds.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpEventIds.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp { public static class AmqpEventIds { - const int EventIdStart = 5000; public const int SaslPlainAuthenticator = EventIdStart; public const int AmqpProtocolHead = EventIdStart + 100; public const int CbsLinkHandler = EventIdStart + 200; @@ -18,5 +17,6 @@ public static class AmqpEventIds public const int AmqpWebSocketListener = EventIdStart + 800; public const int ServerWebSocketTransport = EventIdStart + 900; public const int X509PrinciparAuthenticator = EventIdStart + 1000; + const int EventIdStart = 5000; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpExceptionsHelper.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpExceptionsHelper.cs index e549c8672b8..743f3f92931 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpExceptionsHelper.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpExceptionsHelper.cs @@ -13,7 +13,7 @@ public class AmqpExceptionsHelper { public static AmqpException GetAmqpException(Exception ex) { - // If this exception is an AmqpException with LinkRedirect or NotAllowed errors, return it. + // If this exception is an AmqpException with LinkRedirect or NotAllowed errors, return it. if (ex is AmqpException amqpException) { if (amqpException.Error.Condition.Equals(AmqpErrorCode.LinkRedirect) || amqpException.Error.Condition.Equals(AmqpErrorCode.NotAllowed)) @@ -47,6 +47,7 @@ static EdgeAmqpException GetEdgeHubAmqpException(Exception exception) { return new EdgeAmqpException("Invalid action performed", ErrorCode.InvalidOperation); } + return new EdgeAmqpException("Encountered server error", ErrorCode.ServerError, exception); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageConverter.cs index 747ab6ccd12..12c5ab67478 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageConverter.cs @@ -59,7 +59,7 @@ byte[] GetMessageBody() systemProperties.AddIfNonEmpty(SystemProperties.LockToken, lockToken); } - if(sourceMessage.ApplicationProperties != null) + if (sourceMessage.ApplicationProperties != null) { foreach (KeyValuePair property in sourceMessage.ApplicationProperties.Map) { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageUtils.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageUtils.cs index 4cdb83d3ace..3fc02807425 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageUtils.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpMessageUtils.cs @@ -10,13 +10,14 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp public static class AmqpMessageUtils { + public const string SystemPropertyDiagId = "Diagnostic-Id"; + + public const string SystemPropertyDiagnosticCorrelationContext = "Correlation-Context"; + // All DateTimes in the system are encoded using the format "yyyy-MM-ddTHH:mm:ss.fff" // when represented as strings. This encoding has a constant length. static readonly long Iso8601Length = 24L; - public const string SystemPropertyDiagId = "Diagnostic-Id"; - public const string SystemPropertyDiagnosticCorrelationContext = "Correlation-Context"; - public static long GetMessageSize(AmqpMessage message, bool includeAllMessageAnnotations = false) { long size = 0L; @@ -27,9 +28,19 @@ public static long GetMessageSize(AmqpMessage message, bool includeAllMessageAnn size += GetMessageBodySize(message); size += GetMessageAnnotationsSize(message, includeAllMessageAnnotations); } + return size; } + public static byte[] GetPayloadBytes(this AmqpMessage message) + { + using (var ms = new MemoryStream()) + { + message.BodyStream.CopyTo(ms); + return ms.ToArray(); + } + } + static long GetMessagePropertiesSize(AmqpMessage message) { long size = 0L; @@ -49,6 +60,7 @@ static long GetMessagePropertiesSize(AmqpMessage message) size += message.Properties.ContentEncoding.Value?.Length ?? 0L; size += message.Properties.AbsoluteExpiryTime.HasValue ? Iso8601Length : 0L; } + return size; } @@ -63,6 +75,7 @@ static long GetMessageApplicationPropertiesSize(AmqpMessage message) size += pair.Value?.ToString().Length ?? 0L; } } + return size; } @@ -82,6 +95,7 @@ static long GetMessageAnnotationsSize(AmqpMessage message, bool includeAllMessag } } } + return size; } @@ -93,6 +107,7 @@ static long GetMessageBodySize(AmqpMessage message) { size += message.BodyStream?.Length ?? 0L; } + return size; } @@ -100,14 +115,5 @@ static bool SectionExists(AmqpMessage message, SectionFlag section) { return (message.Sections & section) != 0; } - - public static byte[] GetPayloadBytes(this AmqpMessage message) - { - using (var ms = new MemoryStream()) - { - message.BodyStream.CopyTo(ms); - return ms.ToArray(); - } - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpProtocolHead.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpProtocolHead.cs index b83cb775769..4f42782b6c0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpProtocolHead.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpProtocolHead.cs @@ -113,6 +113,11 @@ public async Task CloseAsync(CancellationToken token) this.SafeCloseExistingConnections(); } + public void Dispose() + { + this.CloseAsync(CancellationToken.None).Wait(); + } + void OnAcceptTransport(TransportListener transportListener, TransportAsyncCallbackArgs args) { if (args.Exception != null) @@ -130,18 +135,19 @@ void OnAcceptTransport(TransportListener transportListener, TransportAsyncCallba (ProtocolHeader)args.UserToken, false, this.amqpSettings.Clone(), - this.connectionSettings.Clone() - ); + this.connectionSettings.Clone()); // Open the connection async but don't block waiting on it. this.OpenAmqpConnectionAsync(connection, AmqpConstants.DefaultTimeout) - .ContinueWith(task => - { - if (task.Exception != null) + .ContinueWith( + task => { - Events.OpenConnectionError(task.Exception); - } - }, TaskContinuationOptions.OnlyOnFaulted); + if (task.Exception != null) + { + Events.OpenConnectionError(task.Exception); + } + }, + TaskContinuationOptions.OnlyOnFaulted); } catch (Exception ex) when (ex.IsFatal() == false) { @@ -221,15 +227,10 @@ void SafeCloseExistingConnections() connectionSnapShot.ForEach(conn => conn.SafeClose(new AmqpException(AmqpErrorCode.DetachForced, "Server busy, please retry operation"))); } - public void Dispose() - { - this.CloseAsync(CancellationToken.None).Wait(); - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.AmqpProtocolHead; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpRuntimeProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpRuntimeProvider.cs index ea781f3a709..6479bf0b31b 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpRuntimeProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpRuntimeProvider.cs @@ -25,9 +25,14 @@ public class AmqpRuntimeProvider : IRuntimeProvider readonly string iotHubHostName; readonly ICredentialsCache credentialsCache; - public AmqpRuntimeProvider(ILinkHandlerProvider linkHandlerProvider, bool requireSecureTransport, - IClientCredentialsFactory clientCredentialsFactory, IAuthenticator authenticator, - string iotHubHostName, IConnectionProvider connectionProvider, ICredentialsCache credentialsCache) + public AmqpRuntimeProvider( + ILinkHandlerProvider linkHandlerProvider, + bool requireSecureTransport, + IClientCredentialsFactory clientCredentialsFactory, + IAuthenticator authenticator, + string iotHubHostName, + IConnectionProvider connectionProvider, + ICredentialsCache credentialsCache) { this.linkHandlerProvider = Preconditions.CheckNotNull(linkHandlerProvider, nameof(linkHandlerProvider)); this.requireSecureTransport = Preconditions.CheckNotNull(requireSecureTransport, nameof(requireSecureTransport)); @@ -59,36 +64,6 @@ AmqpConnection IConnectionFactory.CreateConnection( return connection; } - void OnConnectionOpening(object sender, OpenEventArgs e) - { - var command = (Open)e.Command; - - // 'command.IdleTimeOut' is the Idle time out specified in the client OPEN frame - // Server will send heart beats honoring this timeout(every 7/8 of IdleTimeout) - if (command.IdleTimeOut == null || command.IdleTimeOut == 0) - { - command.IdleTimeOut = Constants.DefaultAmqpHeartbeatSendInterval; - } - else if (command.IdleTimeOut < Constants.MinimumAmqpHeartbeatSendInterval) - { - throw new EdgeHubConnectionException($"Connection idle timeout specified is less than minimum acceptable value: {Constants.MinimumAmqpHeartbeatSendInterval}"); - } - - var amqpConnection = (AmqpConnection)sender; - // If the AmqpConnection does not use username/password or certs, create a CbsNode for the connection - // and add it to the Extensions - if (!(amqpConnection.Principal is SaslPrincipal || amqpConnection.Principal is X509Principal)) - { - ICbsNode cbsNode = new CbsNode(this.clientCredentialsFactory, this.iotHubHostName, this.authenticator, this.credentialsCache); - amqpConnection.Extensions.Add(cbsNode); - } - - IClientConnectionsHandler connectionHandler = new ClientConnectionsHandler(this.connectionProvider); - amqpConnection.Extensions.Add(connectionHandler); - } - - AmqpSession ISessionFactory.CreateSession(AmqpConnection connection, AmqpSessionSettings settings) => new AmqpSession(connection, settings, this); - AmqpLink ILinkFactory.CreateLink(AmqpSession session, AmqpLinkSettings settings) { try @@ -124,7 +99,6 @@ AmqpLink ILinkFactory.CreateLink(AmqpSession session, AmqpLinkSettings settings) // amqp[s]:a/b <-- path relative to hostname specified in OPEN // a/b <-- pre-global addressing style path relative to hostname specified in OPEN // /a/b <-- same as above - Uri linkUri; if (!linkAddress.StartsWith(Constants.AmqpsScheme, StringComparison.OrdinalIgnoreCase)) { @@ -152,6 +126,36 @@ IAsyncResult ILinkFactory.BeginOpenLink(AmqpLink link, TimeSpan timeout, AsyncCa void ILinkFactory.EndOpenLink(IAsyncResult result) => TaskEx.EndAsyncResult(result); + AmqpSession ISessionFactory.CreateSession(AmqpConnection connection, AmqpSessionSettings settings) => new AmqpSession(connection, settings, this); + + void OnConnectionOpening(object sender, OpenEventArgs e) + { + var command = (Open)e.Command; + + // 'command.IdleTimeOut' is the Idle time out specified in the client OPEN frame + // Server will send heart beats honoring this timeout(every 7/8 of IdleTimeout) + if (command.IdleTimeOut == null || command.IdleTimeOut == 0) + { + command.IdleTimeOut = Constants.DefaultAmqpHeartbeatSendInterval; + } + else if (command.IdleTimeOut < Constants.MinimumAmqpHeartbeatSendInterval) + { + throw new EdgeHubConnectionException($"Connection idle timeout specified is less than minimum acceptable value: {Constants.MinimumAmqpHeartbeatSendInterval}"); + } + + var amqpConnection = (AmqpConnection)sender; + // If the AmqpConnection does not use username/password or certs, create a CbsNode for the connection + // and add it to the Extensions + if (!(amqpConnection.Principal is SaslPrincipal || amqpConnection.Principal is X509Principal)) + { + ICbsNode cbsNode = new CbsNode(this.clientCredentialsFactory, this.iotHubHostName, this.authenticator, this.credentialsCache); + amqpConnection.Extensions.Add(cbsNode); + } + + IClientConnectionsHandler connectionHandler = new ClientConnectionsHandler(this.connectionProvider); + amqpConnection.Extensions.Add(connectionHandler); + } + Task OpenLinkAsync(AmqpLink link, TimeSpan timeout) { try diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpTwinMessageConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpTwinMessageConverter.cs index 410f6402c59..f239adb45d4 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpTwinMessageConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpTwinMessageConverter.cs @@ -25,6 +25,7 @@ public AmqpMessage FromMessage(IMessage message) { amqpMessage.MessageAnnotations.Map["status"] = status; } + return amqpMessage; } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpWebSocketListener.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpWebSocketListener.cs index 7c048f42309..5a33f11ab2a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpWebSocketListener.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/AmqpWebSocketListener.cs @@ -7,25 +7,28 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Extensions.Logging; using Microsoft.Azure.Amqp.Transport; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; + using Microsoft.Azure.Devices.Edge.Util; + using Microsoft.Extensions.Logging; class AmqpWebSocketListener : TransportListener, IWebSocketListener { - public string SubProtocol => Constants.WebSocketSubProtocol; readonly IAuthenticator authenticator; readonly IClientCredentialsFactory clientCredentialsFactory; - public AmqpWebSocketListener(IAuthenticator authenticator, - IClientCredentialsFactory clientCredentialsFactory) + + public AmqpWebSocketListener( + IAuthenticator authenticator, + IClientCredentialsFactory clientCredentialsFactory) : base(Constants.WebSocketListenerName) { this.authenticator = Preconditions.CheckNotNull(authenticator, nameof(authenticator)); this.clientCredentialsFactory = Preconditions.CheckNotNull(clientCredentialsFactory, nameof(clientCredentialsFactory)); } + public string SubProtocol => Constants.WebSocketSubProtocol; + public async Task ProcessWebSocketRequestAsync(WebSocket webSocket, Option localEndPoint, EndPoint remoteEndPoint, string correlationId, X509Certificate2 clientCert, IList clientCertChain) { try @@ -36,21 +39,23 @@ public async Task ProcessWebSocketRequestAsync(WebSocket webSocket, Option - { - taskCompletion.SetResult(true); - }; + transport.Closed += (sender, eventArgs) => { taskCompletion.SetResult(true); }; - //wait until websocket is closed + // wait until websocket is closed await taskCompletion.Task; } catch (Exception ex) when (!ex.IsFatal()) @@ -84,8 +86,8 @@ protected override void OnListen() static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.AmqpWebSocketListener; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -100,5 +102,4 @@ public static void FailedAcceptWebSocket(string correlationId, Exception ex) => Log.LogWarning((int)EventIds.Exception, ex, $"Connection failed CorrelationId {correlationId}"); } } - } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/CbsNode.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/CbsNode.cs index f3d97193ee3..f9e4cd4f8e2 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/CbsNode.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/CbsNode.cs @@ -19,8 +19,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using Microsoft.Extensions.Logging; /// - /// This class is used to get tokens from the Client on the CBS link. It generates - /// an identity from the received token and authenticates it. + /// This class is used to get tokens from the Client on the CBS link. It generates + /// an identity from the received token and authenticates it. /// class CbsNode : ICbsNode, IAmqpAuthenticator { @@ -69,6 +69,7 @@ public void RegisterLink(IAmqpLink link) this.sendingLink = (EdgeSendingAmqpLink)link; } } + Events.LinkRegistered(link); } @@ -106,34 +107,76 @@ public async Task AuthenticateAsync(string id) } } - async void OnMessageReceived(AmqpMessage message) + public void Dispose() { - Events.NewTokenReceived(); + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + this.disposed = true; + } + } + + internal static (string token, string audience) ValidateAndParseMessage(string iotHubHostName, AmqpMessage message) + { + string type = message.ApplicationProperties.Map[CbsConstants.PutToken.Type] as string; + if (!CbsConstants.SupportedTokenTypes.Any(t => string.Equals(type, t, StringComparison.OrdinalIgnoreCase))) + { + throw new InvalidOperationException("Cbs message missing Type property"); + } + + if (string.IsNullOrEmpty(message.ApplicationProperties.Map[CbsConstants.PutToken.Audience] as string)) + { + throw new InvalidOperationException("Cbs message missing audience property"); + } + + if (!(message.ApplicationProperties.Map[CbsConstants.Operation] is string operation) + || !operation.Equals(CbsConstants.PutToken.OperationValue, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException($"Cbs message missing operation value {CbsConstants.PutToken.OperationValue}"); + } + + string token = message.ValueBody.Value as string; + if (string.IsNullOrEmpty(token)) + { + throw new InvalidOperationException("Cbs message does not contain a valid token"); + } + try { - await this.HandleTokenUpdate(message); + SharedAccessSignature sharedAccessSignature = SharedAccessSignature.Parse(iotHubHostName, token); + return (token, sharedAccessSignature.Audience); } - catch (Exception ex) + catch (Exception e) { - Events.ErrorHandlingTokenUpdate(ex); + throw new InvalidOperationException("Cbs message does not contain a valid token", e); } } - async Task HandleTokenUpdate(AmqpMessage message) + internal static (string deviceId, string moduleId) ParseIds(string audience) { - using (await this.identitySyncLock.LockAsync()) + string decodedAudience = WebUtility.UrlDecode(audience); + string audienceUri = decodedAudience.StartsWith("amqps://", StringComparison.CurrentCultureIgnoreCase) ? decodedAudience : "amqps://" + decodedAudience; + + foreach (UriPathTemplate template in ResourceTemplates) { - try - { - (AmqpResponseStatusCode statusCode, string description) = await this.UpdateCbsToken(message); - await this.SendResponseAsync(message, statusCode, description); - } - catch (Exception e) + (bool success, IList> boundVariables) = template.Match(new Uri(audienceUri)); + if (success) { - await this.SendResponseAsync(message, AmqpResponseStatusCode.InternalServerError, e.Message); - Events.ErrorUpdatingToken(e); + IDictionary boundVariablesDictionary = boundVariables.ToDictionary(); + string deviceId = boundVariablesDictionary[Templates.DeviceIdTemplateParameterName]; + string moduleId = boundVariablesDictionary.ContainsKey(Templates.ModuleIdTemplateParameterName) + ? boundVariablesDictionary[Templates.ModuleIdTemplateParameterName] + : null; + return (deviceId, moduleId); } } + + throw new InvalidOperationException($"Matching template not found for audience {audienceUri}"); } // Note: This method updates this.clientCredentialsMap, and should be invoked only within this.identitySyncLock @@ -160,6 +203,7 @@ async Task HandleTokenUpdate(AmqpMessage message) { credentialsInfo.ClientCredentials = clientCredentials; } + if (credentialsInfo.IsAuthenticated) { await this.credentialsCache.Add(clientCredentials); @@ -182,54 +226,10 @@ internal IClientCredentials GetClientCredentials(AmqpMessage message) return clientCredentials; } - internal static (string token, string audience) ValidateAndParseMessage(string iotHubHostName, AmqpMessage message) - { - string type = message.ApplicationProperties.Map[CbsConstants.PutToken.Type] as string; - if (!CbsConstants.SupportedTokenTypes.Any(t => string.Equals(type, t, StringComparison.OrdinalIgnoreCase))) - { - throw new InvalidOperationException("Cbs message missing Type property"); - } - - if (string.IsNullOrEmpty(message.ApplicationProperties.Map[CbsConstants.PutToken.Audience] as string)) - { - throw new InvalidOperationException("Cbs message missing audience property"); - } - - if (!(message.ApplicationProperties.Map[CbsConstants.Operation] is string operation) - || !operation.Equals(CbsConstants.PutToken.OperationValue, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException($"Cbs message missing operation value {CbsConstants.PutToken.OperationValue}"); - } - - string token = message.ValueBody.Value as string; - if (string.IsNullOrEmpty(token)) - { - throw new InvalidOperationException("Cbs message does not contain a valid token"); - } - - try - { - SharedAccessSignature sharedAccessSignature = SharedAccessSignature.Parse(iotHubHostName, token); - return (token, sharedAccessSignature.Audience); - } - catch (Exception e) - { - throw new InvalidOperationException("Cbs message does not contain a valid token", e); - } - } - - Task SendResponseAsync(AmqpMessage requestMessage, AmqpResponseStatusCode statusCode, string statusDescription) + internal ArraySegment GetDeliveryTag() { - try - { - AmqpMessage response = GetAmqpResponse(requestMessage, statusCode, statusDescription); - return this.sendingLink?.SendMessageAsync(response, this.GetDeliveryTag(), AmqpConstants.NullBinary, AmqpConstants.DefaultTimeout) ?? Task.CompletedTask; - } - catch (Exception e) - { - Events.ErrorSendingResponse(e); - return Task.CompletedTask; - } + int deliveryId = Interlocked.Increment(ref this.deliveryCount); + return new ArraySegment(BitConverter.GetBytes(deliveryId)); } static AmqpMessage GetAmqpResponse(AmqpMessage requestMessage, AmqpResponseStatusCode statusCode, string statusDescription) @@ -242,50 +242,52 @@ static AmqpMessage GetAmqpResponse(AmqpMessage requestMessage, AmqpResponseStatu return response; } - internal static (string deviceId, string moduleId) ParseIds(string audience) + async void OnMessageReceived(AmqpMessage message) { - string decodedAudience = WebUtility.UrlDecode(audience); - string audienceUri = decodedAudience.StartsWith("amqps://", StringComparison.CurrentCultureIgnoreCase) ? decodedAudience : "amqps://" + decodedAudience; - - foreach (UriPathTemplate template in ResourceTemplates) + Events.NewTokenReceived(); + try { - (bool success, IList> boundVariables) = template.Match(new Uri(audienceUri)); - if (success) - { - IDictionary boundVariablesDictionary = boundVariables.ToDictionary(); - string deviceId = boundVariablesDictionary[Templates.DeviceIdTemplateParameterName]; - string moduleId = boundVariablesDictionary.ContainsKey(Templates.ModuleIdTemplateParameterName) - ? boundVariablesDictionary[Templates.ModuleIdTemplateParameterName] - : null; - return (deviceId, moduleId); - } + await this.HandleTokenUpdate(message); + } + catch (Exception ex) + { + Events.ErrorHandlingTokenUpdate(ex); } - - throw new InvalidOperationException($"Matching template not found for audience {audienceUri}"); - } - - internal ArraySegment GetDeliveryTag() - { - int deliveryId = Interlocked.Increment(ref this.deliveryCount); - return new ArraySegment(BitConverter.GetBytes(deliveryId)); } - public void Dispose() + async Task HandleTokenUpdate(AmqpMessage message) { - this.Dispose(true); - GC.SuppressFinalize(this); + using (await this.identitySyncLock.LockAsync()) + { + try + { + (AmqpResponseStatusCode statusCode, string description) = await this.UpdateCbsToken(message); + await this.SendResponseAsync(message, statusCode, description); + } + catch (Exception e) + { + await this.SendResponseAsync(message, AmqpResponseStatusCode.InternalServerError, e.Message); + Events.ErrorUpdatingToken(e); + } + } } - protected virtual void Dispose(bool disposing) + Task SendResponseAsync(AmqpMessage requestMessage, AmqpResponseStatusCode statusCode, string statusDescription) { - if (!this.disposed) + try { - this.disposed = true; + AmqpMessage response = GetAmqpResponse(requestMessage, statusCode, statusDescription); + return this.sendingLink?.SendMessageAsync(response, this.GetDeliveryTag(), AmqpConstants.NullBinary, AmqpConstants.DefaultTimeout) ?? Task.CompletedTask; + } + catch (Exception e) + { + Events.ErrorSendingResponse(e); + return Task.CompletedTask; } } /// - /// This type is deliberately mutable because of the use case. + /// This type is deliberately mutable because of the use case. /// class CredentialsInfo { @@ -305,8 +307,8 @@ public CredentialsInfo(IClientCredentials clientCredentials) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.CbsNode; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionHandler.cs index 48aedd77222..16f22654117 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionHandler.cs @@ -17,7 +17,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp /// /// This class helps maintain the links on an Amqp connection, and it also acts as a common interface for all links. /// It maintains the IIdentity and the IDeviceListener for the connection, and provides it to the link handlers. - /// It also maintains a registry of the links open on that connection, and makes sure duplicate/invalid links are not opened. + /// It also maintains a registry of the links open on that connection, and makes sure duplicate/invalid links are not opened. /// class ClientConnectionHandler : IConnectionHandler { @@ -76,6 +76,7 @@ public async Task RegisterLinkHandler(ILinkHandler linkHandler) { nonCorrelatedLinkHandler = methodSendingLinkHandler; } + break; case LinkType.MethodSending: @@ -84,6 +85,7 @@ public async Task RegisterLinkHandler(ILinkHandler linkHandler) { nonCorrelatedLinkHandler = methodReceivingLinkHandler; } + break; case LinkType.TwinReceiving: @@ -92,6 +94,7 @@ public async Task RegisterLinkHandler(ILinkHandler linkHandler) { nonCorrelatedLinkHandler = twinSendingLinkHandler; } + break; case LinkType.TwinSending: @@ -100,8 +103,10 @@ public async Task RegisterLinkHandler(ILinkHandler linkHandler) { nonCorrelatedLinkHandler = twinReceivingLinkHandler; } + break; } + await (nonCorrelatedLinkHandler?.CloseAsync(Constants.DefaultTimeout) ?? Task.CompletedTask); this.registry[linkHandler.Type] = linkHandler; } @@ -149,6 +154,10 @@ public DeviceProxy(ClientConnectionHandler clientConnectionHandler, IIdentity id this.Identity = identity; } + public bool IsActive => this.isActive; + + public IIdentity Identity { get; } + public Task CloseAsync(Exception ex) { if (this.isActive.GetAndSet(false)) @@ -156,6 +165,7 @@ public Task CloseAsync(Exception ex) Events.ClosingProxy(this.Identity, ex); return this.clientConnectionHandler.CloseAllLinks(); } + return Task.CompletedTask; } @@ -166,6 +176,7 @@ public Task SendC2DMessageAsync(IMessage message) Events.LinkNotFound(LinkType.ModuleMessages, this.Identity, "C2D message"); return Task.CompletedTask; } + message.SystemProperties[SystemProperties.To] = this.Identity is IModuleIdentity moduleIdentity ? $"/devices/{HttpUtility.UrlEncode(moduleIdentity.DeviceId)}/modules/{HttpUtility.UrlEncode(moduleIdentity.ModuleId)}" : $"/devices/{HttpUtility.UrlEncode(this.Identity.Id)}"; @@ -180,6 +191,7 @@ public Task SendMessageAsync(IMessage message, string input) Events.LinkNotFound(LinkType.ModuleMessages, this.Identity, "message"); return Task.CompletedTask; } + message.SystemProperties[SystemProperties.InputName] = input; Events.SendingTelemetryMessage(this.Identity); return ((ISendingLinkHandler)linkHandler).SendMessage(message); @@ -194,14 +206,16 @@ public async Task InvokeMethodAsync(DirectMethodRequest re } IMessage message = new EdgeMessage.Builder(request.Data) - .SetProperties(new Dictionary - { - [Constants.MessagePropertiesMethodNameKey] = request.Name - }) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = request.CorrelationId - }) + .SetProperties( + new Dictionary + { + [Constants.MessagePropertiesMethodNameKey] = request.Name + }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = request.CorrelationId + }) .Build(); await ((ISendingLinkHandler)linkHandler).SendMessage(message); Events.SentMethodInvocation(this.Identity); @@ -232,10 +246,6 @@ public Task SendTwinUpdate(IMessage twin) return ((ISendingLinkHandler)linkHandler).SendMessage(twin); } - public bool IsActive => this.isActive; - - public IIdentity Identity { get; } - public void SetInactive() { Events.SettingProxyInactive(this.Identity); @@ -247,8 +257,8 @@ public void SetInactive() static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.ConnectionHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -263,49 +273,49 @@ enum EventIds SendingTwinUpdate } - internal static void ClosingProxy(IIdentity identity, Exception ex) + public static void SendingC2DMessage(IIdentity identity) { - Log.LogInformation((int)EventIds.ClosingProxy, ex, $"Closing AMQP device proxy for {identity.Id} because no handler was registered."); + Log.LogDebug((int)EventIds.SendingC2DMessage, $"Sending C2D message to {identity.Id}"); } - internal static void LinkNotFound(LinkType linkType, IIdentity identity, string operation) + public static void SendingTelemetryMessage(IIdentity identity) { - Log.LogWarning((int)EventIds.LinkNotFound, $"Unable to send {operation} to {identity.Id} because {linkType} link was not found."); + Log.LogDebug((int)EventIds.SendingTelemetryMessage, $"Sending telemetry message to {identity.Id}"); } - internal static void SettingProxyInactive(IIdentity identity) + public static void SentMethodInvocation(IIdentity identity) { - Log.LogInformation((int)EventIds.SettingProxyInactive, $"Setting proxy inactive for {identity.Id}."); + Log.LogDebug((int)EventIds.SentMethodInvocation, $"Sending method invocation to {identity.Id}"); } - internal static void InitializedDeviceListener(IIdentity identity) + public static void SendingDeriredPropertyUpdates(IIdentity identity) { - Log.LogInformation((int)EventIds.InitializedDeviceListener, $"Initialized device listener in the AMQP protocol head for {identity.Id}"); + Log.LogDebug((int)EventIds.SendingDeriredPropertyUpdates, $"Sending desired properties update to {identity.Id}"); } - public static void SendingC2DMessage(IIdentity identity) + public static void SendingTwinUpdate(IIdentity identity) { - Log.LogDebug((int)EventIds.SendingC2DMessage, $"Sending C2D message to {identity.Id}"); + Log.LogDebug((int)EventIds.SendingTwinUpdate, $"Sending twin update to {identity.Id}"); } - public static void SendingTelemetryMessage(IIdentity identity) + internal static void ClosingProxy(IIdentity identity, Exception ex) { - Log.LogDebug((int)EventIds.SendingTelemetryMessage, $"Sending telemetry message to {identity.Id}"); + Log.LogInformation((int)EventIds.ClosingProxy, ex, $"Closing AMQP device proxy for {identity.Id} because no handler was registered."); } - public static void SentMethodInvocation(IIdentity identity) + internal static void LinkNotFound(LinkType linkType, IIdentity identity, string operation) { - Log.LogDebug((int)EventIds.SentMethodInvocation, $"Sending method invocation to {identity.Id}"); + Log.LogWarning((int)EventIds.LinkNotFound, $"Unable to send {operation} to {identity.Id} because {linkType} link was not found."); } - public static void SendingDeriredPropertyUpdates(IIdentity identity) + internal static void SettingProxyInactive(IIdentity identity) { - Log.LogDebug((int)EventIds.SendingDeriredPropertyUpdates, $"Sending desired properties update to {identity.Id}"); + Log.LogInformation((int)EventIds.SettingProxyInactive, $"Setting proxy inactive for {identity.Id}."); } - public static void SendingTwinUpdate(IIdentity identity) + internal static void InitializedDeviceListener(IIdentity identity) { - Log.LogDebug((int)EventIds.SendingTwinUpdate, $"Sending twin update to {identity.Id}"); + Log.LogInformation((int)EventIds.InitializedDeviceListener, $"Initialized device listener in the AMQP protocol head for {identity.Id}"); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionsHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionsHandler.cs index 7f7faaeec8f..6f7243c6794 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionsHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ClientConnectionsHandler.cs @@ -18,5 +18,5 @@ public ClientConnectionsHandler(IConnectionProvider connectionProvider) public IConnectionHandler GetConnectionHandler(IIdentity identity) => this.connectionHandlers.GetOrAdd(identity.Id, i => new ClientConnectionHandler(identity, this.connectionProvider)); - } + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Constants.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Constants.cs index 4e83ceb9ab6..6f45b057614 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Constants.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Constants.cs @@ -10,9 +10,6 @@ public static class Constants public const uint DefaultAmqpConnectionIdleTimeoutInMilliSeconds = 4 * 60 * 1000; public const uint MinimumAmqpHeartbeatSendInterval = 5 * 1000; public const uint DefaultAmqpHeartbeatSendInterval = 2 * 60 * 1000; - public static readonly AmqpVersion AmqpVersion100 = new AmqpVersion(1, 0, 0); - - public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); // NOTE: IoT Hub service has this note on this constant: // Temporarily accept messages upto 1Mb in size. Reduce to 256 kb after fixing client behavior @@ -32,7 +29,10 @@ public static class Constants public const string MessageAnnotationsConnectionDeviceId = "iothub-connection-device-id"; public const string MessageAnnotationsConnectionModuleId = "iothub-connection-module-id"; public const string WebSocketSubProtocol = "AMQPWSB10"; - public const string WebSocketListenerName = WebSocketSubProtocol +"-listener"; + public const string WebSocketListenerName = WebSocketSubProtocol + "-listener"; public const string ServiceBusCbsSaslMechanismName = "MSSBCBS"; + + public static readonly AmqpVersion AmqpVersion100 = new AmqpVersion(1, 0, 0); + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpConnection.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpConnection.cs index a0a269f79c7..fd589184f37 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpConnection.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpConnection.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using Microsoft.Azure.Devices.Edge.Util; /// - /// This class wraps an AmqpConnection, and provides similar functionality. + /// This class wraps an AmqpConnection, and provides similar functionality. /// This allows unit testing the components that use it /// public class EdgeAmqpConnection : IAmqpConnection @@ -19,10 +19,10 @@ public EdgeAmqpConnection(AmqpConnection amqpConnection) this.underlyingAmqpConnection = Preconditions.CheckNotNull(amqpConnection, nameof(amqpConnection)); } - public T FindExtension() => this.underlyingAmqpConnection.Extensions.Find(); - public IPrincipal Principal => this.underlyingAmqpConnection.Principal; + public T FindExtension() => this.underlyingAmqpConnection.Extensions.Find(); + public Task Close() => this.underlyingAmqpConnection.CloseAsync(this.underlyingAmqpConnection.DefaultCloseTimeout); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpException.cs index cdb59eaefc7..ab95c67639b 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpException.cs @@ -8,7 +8,8 @@ public class EdgeAmqpException : Exception { public EdgeAmqpException(string message, ErrorCode errorCode) : this(message, errorCode, null) - { } + { + } public EdgeAmqpException(string message, ErrorCode errorCode, Exception innerException) : base(message, innerException) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpLink.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpLink.cs index 7e7144ce289..bb227a8ac27 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpLink.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpLink.cs @@ -8,7 +8,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using Microsoft.Azure.Devices.Edge.Util; /// - /// This class wraps an AmqpLink, and provides similar functionality. + /// This class wraps an AmqpLink, and provides similar functionality. /// This allows unit testing the components that use it /// public abstract class EdgeAmqpLink : IAmqpLink @@ -19,22 +19,22 @@ protected EdgeAmqpLink(AmqpLink amqpLink) this.Session = new EdgeAmqpSession(this.AmqpLink.Session); } - protected AmqpLink AmqpLink { get; } - - public void SafeAddClosed(EventHandler handler) => this.AmqpLink.SafeAddClosed(handler); - public bool IsReceiver => this.AmqpLink.IsReceiver; public IAmqpSession Session { get; set; } public AmqpObjectState State => this.AmqpLink.State; + public AmqpLinkSettings Settings => this.AmqpLink.Settings; + + protected AmqpLink AmqpLink { get; } + + public void SafeAddClosed(EventHandler handler) => this.AmqpLink.SafeAddClosed(handler); + public bool IsCbsLink() => this.AmqpLink.IsReceiver ? ((Target)this.AmqpLink.Settings.Target).Address.ToString().StartsWith(CbsConstants.CbsAddress, StringComparison.OrdinalIgnoreCase) : ((Source)this.AmqpLink.Settings.Source).Address.ToString().StartsWith(CbsConstants.CbsAddress, StringComparison.OrdinalIgnoreCase); - public AmqpLinkSettings Settings => this.AmqpLink.Settings; - public Task CloseAsync(TimeSpan timeout) => this.AmqpLink.CloseAsync(timeout); } @@ -42,7 +42,8 @@ public class EdgeReceivingAmqpLink : EdgeAmqpLink, IReceivingAmqpLink { public EdgeReceivingAmqpLink(ReceivingAmqpLink amqpLink) : base(amqpLink) - { } + { + } public void RegisterMessageListener(Action onMessageReceived) => ((ReceivingAmqpLink)this.AmqpLink).RegisterMessageListener(onMessageReceived); @@ -54,7 +55,8 @@ public class EdgeSendingAmqpLink : EdgeAmqpLink, ISendingAmqpLink { public EdgeSendingAmqpLink(SendingAmqpLink amqpLink) : base(amqpLink) - { } + { + } public Task SendMessageAsync(AmqpMessage message, ArraySegment deliveryTag, ArraySegment txnId, TimeSpan timeout) => ((SendingAmqpLink)this.AmqpLink).SendMessageAsync(message, deliveryTag, txnId, timeout); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpSession.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpSession.cs index 1f7b49923e5..beac67e70d3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpSession.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeAmqpSession.cs @@ -5,7 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using Microsoft.Azure.Devices.Edge.Util; /// - /// This class wraps an AmqpSession, and provides similar functionality. + /// This class wraps an AmqpSession, and provides similar functionality. /// This allows unit testing the components that use it /// public class EdgeAmqpSession : IAmqpSession diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeSaslPlainAuthenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeSaslPlainAuthenticator.cs index d0c3036b92f..d94a31c6bbd 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeSaslPlainAuthenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeSaslPlainAuthenticator.cs @@ -2,7 +2,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp { using System; - using IPrincipal = System.Security.Principal.IPrincipal; + using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Azure.Amqp.Sasl; using Microsoft.Azure.Devices.Edge.Hub.Core; @@ -62,8 +62,8 @@ public async Task AuthenticateAsync(string identity, string password static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.SaslPlainAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeTlsTransport.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeTlsTransport.cs index 06b2ae9d53a..9d1a36ed3d0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeTlsTransport.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeTlsTransport.cs @@ -15,7 +15,7 @@ public class EdgeTlsTransport : TlsTransport { readonly IClientCredentialsFactory clientCredentialsProvider; readonly IAuthenticator authenticator; - private IList remoteCertificateChain; + IList remoteCertificateChain; public EdgeTlsTransport( TransportBase innerTransport, @@ -31,10 +31,11 @@ public EdgeTlsTransport( protected override X509Principal CreateX509Principal(X509Certificate2 certificate) { - var principal = new EdgeX509Principal(new X509CertificateIdentity(certificate, true), - this.remoteCertificateChain, - this.authenticator, - this.clientCredentialsProvider); + var principal = new EdgeX509Principal( + new X509CertificateIdentity(certificate, true), + this.remoteCertificateChain, + this.authenticator, + this.clientCredentialsProvider); // release chain elements from here since principal has this this.remoteCertificateChain = null; return principal; @@ -42,9 +43,8 @@ protected override X509Principal CreateX509Principal(X509Certificate2 certificat protected override bool ValidateRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { - // copy of the chain elements since they are destroyed after this method completes - this.remoteCertificateChain = chain == null ? new List() : - chain.ChainElements.Cast().Select(element => element.Certificate).ToList(); + // copy of the chain elements since they are destroyed after this method completes + this.remoteCertificateChain = chain == null ? new List() : chain.ChainElements.Cast().Select(element => element.Certificate).ToList(); return base.ValidateRemoteCertificate(sender, certificate, chain, sslPolicyErrors); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeX509Principal.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeX509Principal.cs index 29adb613263..8f97ddb975e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeX509Principal.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/EdgeX509Principal.cs @@ -5,12 +5,12 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - using static System.FormattableString; using Microsoft.Azure.Amqp.X509; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; + using static System.FormattableString; class EdgeX509Principal : X509Principal, IAmqpAuthenticator { @@ -41,6 +41,7 @@ public async Task AuthenticateAsync(string id) Events.IdentityParseFailed(id); return false; } + IClientCredentials clientCredentials = this.clientCredentialsProvider.GetWithX509Cert(identity.deviceId, identity.moduleId, string.Empty, this.CertificateIdentity.Certificate, this.chainCertificates); bool result = await this.authenticator.AuthenticateAsync(clientCredentials); @@ -63,10 +64,12 @@ internal static (bool result, string deviceId, string moduleId) ParseIdentity(st { return (false, string.Empty, string.Empty); } + if (string.IsNullOrWhiteSpace(clientIdParts[0])) { return (false, string.Empty, string.Empty); } + string deviceId = clientIdParts[0]; string moduleId = string.Empty; if (clientIdParts.Length == 2) @@ -86,8 +89,8 @@ internal static (bool result, string deviceId, string moduleId) ParseIdentity(st static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.X509PrinciparAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpConnection.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpConnection.cs index 04c1164b9ea..122b1a39181 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpConnection.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpConnection.cs @@ -5,15 +5,15 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using System.Threading.Tasks; /// - /// This interface contains functionality similar to AmqpConnection. + /// This interface contains functionality similar to AmqpConnection. /// This allows unit testing the components that use it /// public interface IAmqpConnection { - T FindExtension(); - IPrincipal Principal { get; } + T FindExtension(); + Task Close(); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpLink.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpLink.cs index 0a54e7dc206..e14cf04459d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpLink.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpLink.cs @@ -7,28 +7,28 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp using Microsoft.Azure.Amqp.Framing; /// - /// This interface contains functionality similar to AmqpLink. + /// This interface contains functionality similar to AmqpLink. /// This allows unit testing the components that use it /// public interface IAmqpLink { - void SafeAddClosed(EventHandler handler); - bool IsReceiver { get; } IAmqpSession Session { get; set; } AmqpObjectState State { get; } - bool IsCbsLink(); - AmqpLinkSettings Settings { get; } + void SafeAddClosed(EventHandler handler); + + bool IsCbsLink(); + Task CloseAsync(TimeSpan timeout); } /// - /// This interface contains functionality similar to ReceivingAmqpLink. + /// This interface contains functionality similar to ReceivingAmqpLink. /// Created mainly for testing purposes /// public interface IReceivingAmqpLink : IAmqpLink @@ -39,7 +39,7 @@ public interface IReceivingAmqpLink : IAmqpLink } /// - /// This interface contains functionality similar to SendingAmqpLink. + /// This interface contains functionality similar to SendingAmqpLink. /// Created mainly for testing purposes /// public interface ISendingAmqpLink : IAmqpLink diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpSession.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpSession.cs index b624d26f1d9..14f0671e8ca 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpSession.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/IAmqpSession.cs @@ -2,7 +2,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp { /// - /// This interface contains functionality similar to AmqpSession. + /// This interface contains functionality similar to AmqpSession. /// This allows unit testing the components that use it /// public interface IAmqpSession diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Microsoft.Azure.Devices.Edge.Hub.Amqp.csproj b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Microsoft.Azure.Devices.Edge.Hub.Amqp.csproj index a57ab976cc6..c8cd8d3dae7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Microsoft.Azure.Devices.Edge.Hub.Amqp.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/Microsoft.Azure.Devices.Edge.Hub.Amqp.csproj @@ -25,4 +25,12 @@ + + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityHelper.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityHelper.cs index 7436901d91c..ddb0228755b 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityHelper.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityHelper.cs @@ -44,7 +44,6 @@ public static (string deviceId, string moduleId, string iotHubName) Parse(string { throw new EdgeHubConnectionException("Should be @ for device scope or @root. for device hub scope"); } - } return (deviceId, moduleId, iotHubName); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityType.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityType.cs index 4f156869afc..9551f6f8bfa 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityType.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/SaslIdentityType.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp { // TODO: What is the difference between these 2 enum values? - public enum SaslIdentityType { UsernameAndPassword, diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ServerWebSocketTransport.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ServerWebSocketTransport.cs index d906a0b2a4a..4b7b3781ea4 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ServerWebSocketTransport.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/ServerWebSocketTransport.cs @@ -24,12 +24,12 @@ class ServerWebSocketTransport : TransportBase readonly CancellationTokenSource cancellationTokenSource; bool socketAborted; - public ServerWebSocketTransport(WebSocket webSocket, - string localEndpoint, - string remoteEndpoint, - string correlationId) + public ServerWebSocketTransport( + WebSocket webSocket, + string localEndpoint, + string remoteEndpoint, + string correlationId) : base("serverwebsocket") - { this.webSocket = Preconditions.CheckNotNull(webSocket, nameof(webSocket)); this.LocalEndPoint = Preconditions.CheckNotNull(localEndpoint, nameof(localEndpoint)); @@ -38,14 +38,15 @@ public ServerWebSocketTransport(WebSocket webSocket, this.cancellationTokenSource = new CancellationTokenSource(); } - public ServerWebSocketTransport(WebSocket webSocket, - string localEndpoint, - string remoteEndpoint, - string correlationId, - X509Certificate2 clientCert, - IList clientCertChain, - IAuthenticator authenticator, - IClientCredentialsFactory clientCredentialsProvider) + public ServerWebSocketTransport( + WebSocket webSocket, + string localEndpoint, + string remoteEndpoint, + string correlationId, + X509Certificate2 clientCert, + IList clientCertChain, + IAuthenticator authenticator, + IClientCredentialsFactory clientCredentialsProvider) : this(webSocket, localEndpoint, remoteEndpoint, correlationId) { Preconditions.CheckNotNull(authenticator, nameof(authenticator)); @@ -90,6 +91,74 @@ public override bool WriteAsync(TransportAsyncCallbackArgs args) return true; } + public override bool ReadAsync(TransportAsyncCallbackArgs args) + { + Preconditions.CheckNotNull(args.Buffer, nameof(args.Buffer)); + Preconditions.CheckNotNull(args.CompletedCallback, nameof(args.CompletedCallback)); + + this.ValidateIsOpen(); + this.ValidateBufferBounds(args.Buffer, args.Offset, args.Count); + args.Exception = null; + + Task taskResult = this.ReadAsyncCore(args); + if (this.ReadTaskDone(taskResult, args)) + { + return false; + } + + taskResult.ContinueWith( + _ => + { + this.ReadTaskDone(taskResult, args); + args.CompletedCallback(args); + }, + TaskContinuationOptions.ExecuteSynchronously); + + return true; + } + + protected override bool OpenInternal() + { + this.ValidateIsOpen(); + + return true; + } + + protected override bool CloseInternal() + { + WebSocketState webSocketState = this.webSocket.State; + if (webSocketState != WebSocketState.Closed && webSocketState != WebSocketState.Aborted) + { + Events.TransportClosed(this.correlationId); + + this.CloseInternalAsync(TimeSpan.FromSeconds(30)).ContinueWith( + t => { Events.CloseException(this.correlationId, t.Exception); }, + TaskContinuationOptions.OnlyOnFaulted); + } + else + { + Events.TransportAlreadyClosedOrAborted(this.correlationId, webSocketState.ToString()); + } + + return true; + } + + protected override void AbortInternal() + { + WebSocketState webSocketState = this.webSocket.State; + if (!this.socketAborted && webSocketState != WebSocketState.Aborted) + { + Events.TransportAborted(this.correlationId); + this.socketAborted = true; + this.webSocket.Abort(); + this.webSocket.Dispose(); + } + else + { + Events.TransportAlreadyClosedOrAborted(this.correlationId, webSocketState.ToString()); + } + } + async Task WriteAsyncCore(TransportAsyncCallbackArgs args) { bool succeeded = false; @@ -104,8 +173,11 @@ async Task WriteAsyncCore(TransportAsyncCallbackArgs args) { foreach (ByteBuffer byteBuffer in args.ByteBufferList) { - await this.webSocket.SendAsync(new ArraySegment(byteBuffer.Buffer, byteBuffer.Offset, byteBuffer.Length), - WebSocketMessageType.Binary, true, this.cancellationTokenSource.Token); + await this.webSocket.SendAsync( + new ArraySegment(byteBuffer.Buffer, byteBuffer.Offset, byteBuffer.Length), + WebSocketMessageType.Binary, + true, + this.cancellationTokenSource.Token); } } @@ -135,37 +207,14 @@ await this.webSocket.SendAsync(new ArraySegment(byteBuffer.Buffer, byteBuf } } - public override bool ReadAsync(TransportAsyncCallbackArgs args) - { - Preconditions.CheckNotNull(args.Buffer, nameof(args.Buffer)); - Preconditions.CheckNotNull(args.CompletedCallback, nameof(args.CompletedCallback)); - - this.ValidateIsOpen(); - this.ValidateBufferBounds(args.Buffer, args.Offset, args.Count); - args.Exception = null; - - Task taskResult = this.ReadAsyncCore(args); - if (this.ReadTaskDone(taskResult, args)) - { - return false; - } - - taskResult.ContinueWith(_ => - { - this.ReadTaskDone(taskResult, args); - args.CompletedCallback(args); - }, TaskContinuationOptions.ExecuteSynchronously); - - return true; - } - async Task ReadAsyncCore(TransportAsyncCallbackArgs args) { bool succeeded = false; try { WebSocketReceiveResult receiveResult = await this.webSocket.ReceiveAsync( - new ArraySegment(args.Buffer, args.Offset, args.Count), this.cancellationTokenSource.Token); + new ArraySegment(args.Buffer, args.Offset, args.Count), + this.cancellationTokenSource.Token); succeeded = true; return receiveResult.Count; @@ -186,35 +235,6 @@ async Task ReadAsyncCore(TransportAsyncCallbackArgs args) return 0; } - protected override bool OpenInternal() - { - this.ValidateIsOpen(); - - return true; - } - - protected override bool CloseInternal() - { - WebSocketState webSocketState = this.webSocket.State; - if (webSocketState != WebSocketState.Closed && webSocketState != WebSocketState.Aborted) - { - Events.TransportClosed(this.correlationId); - - this.CloseInternalAsync(TimeSpan.FromSeconds(30)).ContinueWith( - t => - { - Events.CloseException(this.correlationId, t.Exception); - }, TaskContinuationOptions.OnlyOnFaulted); - - } - else - { - Events.TransportAlreadyClosedOrAborted(this.correlationId, webSocketState.ToString()); - } - - return true; - } - async Task CloseInternalAsync(TimeSpan timeout) { try @@ -233,22 +253,6 @@ async Task CloseInternalAsync(TimeSpan timeout) this.Abort(); } - protected override void AbortInternal() - { - WebSocketState webSocketState = this.webSocket.State; - if (!this.socketAborted && webSocketState != WebSocketState.Aborted) - { - Events.TransportAborted(this.correlationId); - this.socketAborted = true; - this.webSocket.Abort(); - this.webSocket.Dispose(); - } - else - { - Events.TransportAlreadyClosedOrAborted(this.correlationId, webSocketState.ToString()); - } - } - bool ReadTaskDone(Task taskResult, TransportAsyncCallbackArgs args) { IAsyncResult result = taskResult; @@ -264,7 +268,7 @@ bool ReadTaskDone(Task taskResult, TransportAsyncCallbackArgs args) args.CompletedSynchronously = result.CompletedSynchronously; return true; } - else if (taskResult.IsCanceled) // This should not happen since TaskCanceledException is handled in ReadAsyncCore. + else if (taskResult.IsCanceled) // This should not happen since TaskCanceledException is handled in ReadAsyncCore. { return true; } @@ -305,7 +309,7 @@ bool WriteTaskDone(Task taskResult, TransportAsyncCallbackArgs args) args.CompletedSynchronously = result.CompletedSynchronously; return true; } - else if (taskResult.IsCanceled) // This should not happen since TaskCanceledException is handled in WriteAsyncCore. + else if (taskResult.IsCanceled) // This should not happen since TaskCanceledException is handled in WriteAsyncCore. { return true; } @@ -357,8 +361,8 @@ void ValidateBufferBounds(byte[] buffer, int offset, int size) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.ServerWebSocketTransport; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -385,11 +389,6 @@ public static void TransportClosed(string correlationId) Log.LogInformation((int)EventIds.TransportClosed, $"Connection closed CorrelationId {correlationId}"); } - internal static void TransportAlreadyClosedOrAborted(string correlationId, string state) - { - Log.LogInformation((int)EventIds.TransportAlreadyClosedOrAborted, $"Connection already closed or aborted CorrelationId {correlationId} State {state}"); - } - public static void CloseException(string correlationId, Exception ex) { Log.LogWarning((int)EventIds.CloseException, ex, $"Websockets close failed CorrelationId {correlationId}"); @@ -399,6 +398,11 @@ public static void TransportAborted(string correlationId) { Log.LogInformation((int)EventIds.TransportAborted, $"Connection aborted CorrelationId {correlationId}"); } + + internal static void TransportAlreadyClosedOrAborted(string correlationId, string state) + { + Log.LogInformation((int)EventIds.TransportAlreadyClosedOrAborted, $"Connection already closed or aborted CorrelationId {correlationId} State {state}"); + } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/UriPathTemplate.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/UriPathTemplate.cs index d45a441861f..65570938343 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/UriPathTemplate.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/UriPathTemplate.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp public class UriPathTemplate { + public static readonly char[] PathSegmentTerminationCharacters = { PathSeparator }; const char PathSeparator = '/'; const char VariableNameValueSeparator = '='; const char WildcardCharacter = '*'; @@ -18,8 +19,6 @@ public class UriPathTemplate const char PeriodCharacter = '.'; const int EstimatedVariableValueLength = 20; - public static readonly char[] PathSegmentTerminationCharacters = { PathSeparator }; - TemplatePart[] parts; int projectedLength; Regex pattern; @@ -46,6 +45,7 @@ public string Bind(IDictionary variables) { continue; } + if (result.Length > 0 && result[result.Length - 1] == PathSeparator && partValue[0] == PathSeparator) { result.Append(partValue, 1, partValue.Length - 1); @@ -55,6 +55,7 @@ public string Bind(IDictionary variables) result.Append(partValue); } } + return result.ToString(); } @@ -114,12 +115,14 @@ void Compile(string template) { throw new InvalidOperationException("Wildcard variable can only be used at the end of the template."); } + nameOffset = 1; } else { nameOffset = 0; } + string varName; if (eqIndex == -1) { @@ -129,6 +132,7 @@ void Compile(string template) { varName = varDefinition.Substring(nameOffset, eqIndex); } + string varDefaultValue = eqIndex == -1 ? null : varDefinition.Substring(eqIndex + 1); if (varStartIndex > index) @@ -138,6 +142,7 @@ void Compile(string template) patternStringBuilder.Append(Regex.Escape(template.Substring(index, partLength))); initialCapacity += partLength; } + templateParts.Add(new TemplatePart(varName, varDefaultValue)); this.variablesName.Add(varName); patternStringBuilder.Append("([^/]*)"); @@ -173,10 +178,6 @@ void Compile(string template) struct TemplatePart { - string VariableName { get; } - - string Value { get; } - public TemplatePart(string value) { Preconditions.CheckNotNull(value, nameof(value)); @@ -193,6 +194,10 @@ public TemplatePart(string variableName, string defaultValue) this.Value = defaultValue; } + string VariableName { get; } + + string Value { get; } + public string Bind(IDictionary variables) { if (this.VariableName == null) @@ -207,8 +212,10 @@ public string Bind(IDictionary variables) { throw new InvalidOperationException("Variable was not provided and has no default value to fallback to."); } + return this.Value; } + return variableValue; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/CbsLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/CbsLinkHandler.cs index 93d34b700e8..dc79c3b0eb8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/CbsLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/CbsLinkHandler.cs @@ -24,6 +24,8 @@ public class CbsLinkHandler : ILinkHandler public LinkType Type => LinkType.Cbs; + public string CorrelationId { get; } = Guid.NewGuid().ToString(); + public static ILinkHandler Create(IAmqpLink amqpLink, Uri requestUri) { var cbsNode = amqpLink.Session.Connection.FindExtension(); @@ -44,14 +46,12 @@ public Task CloseAsync(TimeSpan timeout) return Task.CompletedTask; } - public string CorrelationId { get; } = Guid.NewGuid().ToString(); - public Task OpenAsync(TimeSpan timeout) => Task.CompletedTask; static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.CbsLinkHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/EventsLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/EventsLinkHandler.cs index 509d2012521..4e257c8637e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/EventsLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/EventsLinkHandler.cs @@ -68,20 +68,6 @@ protected override async Task OnMessageReceived(AmqpMessage amqpMessage) } } - void AddMessageSystemProperties(IMessage message) - { - if (this.Identity is IDeviceIdentity deviceIdentity) - { - message.SystemProperties[SystemProperties.ConnectionDeviceId] = deviceIdentity.DeviceId; - } - - if (this.Identity is IModuleIdentity moduleIdentity) - { - message.SystemProperties[SystemProperties.ConnectionDeviceId] = moduleIdentity.DeviceId; - message.SystemProperties[SystemProperties.ConnectionModuleId] = moduleIdentity.ModuleId; - } - } - internal static IList ExpandBatchedMessage(AmqpMessage message) { var outputMessages = new List(); @@ -104,9 +90,23 @@ internal static IList ExpandBatchedMessage(AmqpMessage message) return outputMessages; } + void AddMessageSystemProperties(IMessage message) + { + if (this.Identity is IDeviceIdentity deviceIdentity) + { + message.SystemProperties[SystemProperties.ConnectionDeviceId] = deviceIdentity.DeviceId; + } + + if (this.Identity is IModuleIdentity moduleIdentity) + { + message.SystemProperties[SystemProperties.ConnectionDeviceId] = moduleIdentity.DeviceId; + message.SystemProperties[SystemProperties.ConnectionModuleId] = moduleIdentity.ModuleId; + } + } + void HandleException(Exception ex, AmqpMessage incoming, IList outgoing) { - // Get AmqpException + // Get AmqpException AmqpException amqpException = AmqpExceptionsHelper.GetAmqpException(ex); var rejected = new Rejected { Error = amqpException.Error }; ((IReceivingAmqpLink)this.Link).DisposeMessage(incoming, rejected, true, true); @@ -125,8 +125,8 @@ void HandleException(Exception ex, AmqpMessage incoming, IList outg static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.EventsLinkHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ILinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ILinkHandler.cs index a1ea7d82386..ccdd6b2fe3b 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ILinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ILinkHandler.cs @@ -10,12 +10,12 @@ public interface ILinkHandler IAmqpLink Link { get; } - Task OpenAsync(TimeSpan timeout); - - Task CloseAsync(TimeSpan timeout); - string CorrelationId { get; } LinkType Type { get; } + + Task OpenAsync(TimeSpan timeout); + + Task CloseAsync(TimeSpan timeout); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandler.cs index 24be1d57765..278978ac6ed 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandler.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.LinkHandlers using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Amqp; - using Microsoft.Azure.Devices.Edge.Hub.Amqp; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; @@ -33,6 +32,14 @@ protected LinkHandler( this.connectionHandler = Preconditions.CheckNotNull(connectionHandler, nameof(connectionHandler)); } + public IAmqpLink Link { get; } + + public Uri LinkUri { get; } + + public abstract LinkType Type { get; } + + public virtual string CorrelationId { get; } = Guid.NewGuid().ToString(); + protected IIdentity Identity { get; } protected string ClientId => this.Identity.Id; @@ -43,14 +50,6 @@ protected LinkHandler( protected IDictionary BoundVariables { get; } - public IAmqpLink Link { get; } - - public Uri LinkUri { get; } - - public abstract LinkType Type { get; } - - public virtual string CorrelationId { get; } = Guid.NewGuid().ToString(); - public async Task OpenAsync(TimeSpan timeout) { if (!await this.Authenticate()) @@ -65,6 +64,12 @@ public async Task OpenAsync(TimeSpan timeout) Events.Opened(this); } + public async Task CloseAsync(TimeSpan timeout) + { + await this.Link.CloseAsync(timeout); + Events.Closed(this); + } + protected abstract Task OnOpenAsync(TimeSpan timeout); protected Task Authenticate() @@ -95,16 +100,10 @@ protected virtual void OnLinkClosed(object sender, EventArgs args) this.connectionHandler.RemoveLinkHandler(this); } - public async Task CloseAsync(TimeSpan timeout) - { - await this.Link.CloseAsync(timeout); - Events.Closed(this); - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.LinkHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandlerProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandlerProvider.cs index 4c581ff8651..e3c9188dd6c 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandlerProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/LinkHandlerProvider.cs @@ -108,6 +108,26 @@ internal ILinkHandler GetLinkHandler(LinkType linkType, IAmqpLink link, Uri uri, } } + internal (LinkType LinkType, IDictionary BoundVariables) GetLinkType(IAmqpLink link, Uri uri) + { + foreach ((UriPathTemplate Template, bool IsReceiver) key in this.templatesList.Keys) + { + if (TryMatchTemplate(uri, key.Template, out IList> boundVariables) && link.IsReceiver == key.IsReceiver) + { + return (this.templatesList[key], boundVariables.ToDictionary()); + } + } + + throw new InvalidOperationException($"Matching template not found for uri {uri}"); + } + + static bool TryMatchTemplate(Uri uri, UriPathTemplate template, out IList> boundVariables) + { + bool success; + (success, boundVariables) = template.Match(new Uri(uri.AbsolutePath, UriKind.Relative)); + return success; + } + IConnectionHandler GetConnectionHandler(IAmqpLink link, IIdentity identity) { var amqpClientConnectionsHandler = link.Session.Connection.FindExtension(); @@ -131,25 +151,5 @@ internal IIdentity GetIdentity(IDictionary boundVariables) ? this.identityProvider.Create(deviceId, WebUtility.UrlDecode(moduleId)) : this.identityProvider.Create(deviceId); } - - internal (LinkType LinkType, IDictionary BoundVariables) GetLinkType(IAmqpLink link, Uri uri) - { - foreach ((UriPathTemplate Template, bool IsReceiver) key in this.templatesList.Keys) - { - if (TryMatchTemplate(uri, key.Template, out IList> boundVariables) && link.IsReceiver == key.IsReceiver) - { - return (this.templatesList[key], boundVariables.ToDictionary()); - } - } - - throw new InvalidOperationException($"Matching template not found for uri {uri}"); - } - - static bool TryMatchTemplate(Uri uri, UriPathTemplate template, out IList> boundVariables) - { - bool success; - (success, boundVariables) = template.Match(new Uri(uri.AbsolutePath, UriKind.Relative)); - return success; - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodReceivingLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodReceivingLinkHandler.cs index e907cda923f..3669ed853c6 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodReceivingLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodReceivingLinkHandler.cs @@ -28,11 +28,11 @@ public MethodReceivingLinkHandler( public override LinkType Type => LinkType.MethodReceiving; - protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; - public override string CorrelationId => AmqpConnectionUtils.GetCorrelationId(this.Link); + protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; + protected override async Task OnMessageReceived(AmqpMessage amqpMessage) { IMessage message = this.MessageConverter.ToMessage(amqpMessage); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodSendingLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodSendingLinkHandler.cs index eec639ad901..545ba590c38 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodSendingLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/MethodSendingLinkHandler.cs @@ -29,11 +29,11 @@ public MethodSendingLinkHandler( public override LinkType Type => LinkType.MethodSending; - protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; - public override string CorrelationId => AmqpConnectionUtils.GetCorrelationId(this.Link); + protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; + protected override async Task OnOpenAsync(TimeSpan timeout) { await base.OnOpenAsync(timeout); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ReceivingLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ReceivingLinkHandler.cs index 2b6a7dc9414..b72158eed42 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ReceivingLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/ReceivingLinkHandler.cs @@ -71,12 +71,6 @@ protected override Task OnOpenAsync(TimeSpan timeout) return Task.CompletedTask; } - Task OnReceiveLinkClosed() - { - this.sendMessageProcessor.Complete(); - return Task.CompletedTask; - } - protected abstract Task OnMessageReceived(AmqpMessage amqpMessage); internal async Task ProcessMessageAsync(AmqpMessage amqpMessage) @@ -103,10 +97,16 @@ internal async Task ProcessMessageAsync(AmqpMessage amqpMessage) } } + Task OnReceiveLinkClosed() + { + this.sendMessageProcessor.Complete(); + return Task.CompletedTask; + } + static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.ReceivingLinkHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/SendingLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/SendingLinkHandler.cs index 61436616907..43bc3193b3a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/SendingLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/SendingLinkHandler.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.LinkHandlers using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Amqp.Constants; /// /// Base class for all link handlers that send messages to connected devices/modules @@ -33,6 +34,39 @@ protected SendingLinkHandler( protected abstract QualityOfService QualityOfService { get; } + public Task SendMessage(IMessage message) + { + if (this.Link.State != AmqpObjectState.Opened) + { + Events.InvalidLinkState(this); + return Task.CompletedTask; + } + + try + { + AmqpMessage amqpMessage = this.MessageConverter.FromMessage(message); + ArraySegment deliveryTag = amqpMessage.DeliveryTag.Count == 0 + ? new ArraySegment(Guid.NewGuid().ToByteArray()) + : amqpMessage.DeliveryTag; + if (this.QualityOfService != QualityOfService.AtMostOnce) + { + this.SendingAmqpLink.SendMessageNoWait(amqpMessage, deliveryTag, AmqpConstants.NullBinary); + } + else + { + return this.SendingAmqpLink.SendMessageAsync(amqpMessage, deliveryTag, AmqpConstants.NullBinary, Constants.DefaultTimeout); + } + + Events.MessageSent(this, message); + } + catch (Exception ex) + { + Events.ErrorProcessingMessage(ex, this); + } + + return Task.CompletedTask; + } + protected override Task OnOpenAsync(TimeSpan timeout) { switch (this.QualityOfService) @@ -56,7 +90,7 @@ protected override Task OnOpenAsync(TimeSpan timeout) case QualityOfService.AtMostOnce: // The Receiver will spontaneously settle all incoming transfers. this.SendingAmqpLink.Settings.RcvSettleMode = (byte)ReceiverSettleMode.First; - // The Sender will send all deliveries settled to the receiver. + // The Sender will send all deliveries settled to the receiver. this.SendingAmqpLink.Settings.SndSettleMode = (byte)SenderSettleMode.Settled; break; } @@ -64,37 +98,24 @@ protected override Task OnOpenAsync(TimeSpan timeout) return Task.CompletedTask; } - public Task SendMessage(IMessage message) + internal static FeedbackStatus GetFeedbackStatus(Delivery delivery) { - if (this.Link.State != AmqpObjectState.Opened) + if (delivery.State.DescriptorCode == AmqpConstants.AcceptedOutcome.DescriptorCode) { - Events.InvalidLinkState(this); - return Task.CompletedTask; + return FeedbackStatus.Complete; } - - try + else if (delivery.State.DescriptorCode == AmqpConstants.RejectedOutcome.DescriptorCode) { - AmqpMessage amqpMessage = this.MessageConverter.FromMessage(message); - ArraySegment deliveryTag = amqpMessage.DeliveryTag.Count == 0 - ? new ArraySegment(Guid.NewGuid().ToByteArray()) - : amqpMessage.DeliveryTag; - if (this.QualityOfService != QualityOfService.AtMostOnce) - { - this.SendingAmqpLink.SendMessageNoWait(amqpMessage, deliveryTag, AmqpConstants.NullBinary); - } - else - { - return this.SendingAmqpLink.SendMessageAsync(amqpMessage, deliveryTag, AmqpConstants.NullBinary, Amqp.Constants.DefaultTimeout); - } - - Events.MessageSent(this, message); + return FeedbackStatus.Reject; } - catch (Exception ex) + else if (delivery.State.DescriptorCode == AmqpConstants.ReleasedOutcome.DescriptorCode) { - Events.ErrorProcessingMessage(ex, this); + return FeedbackStatus.Abandon; + } + else + { + throw new InvalidOperationException($"Unknown disposition outcome - {delivery.State.DescriptorCode}"); } - - return Task.CompletedTask; } internal void DispositionListener(Delivery delivery) => this.DisposeMessageAsync(delivery); @@ -117,30 +138,10 @@ async void DisposeMessageAsync(Delivery delivery) } } - internal static FeedbackStatus GetFeedbackStatus(Delivery delivery) - { - if (delivery.State.DescriptorCode == AmqpConstants.AcceptedOutcome.DescriptorCode) - { - return FeedbackStatus.Complete; - } - else if (delivery.State.DescriptorCode == AmqpConstants.RejectedOutcome.DescriptorCode) - { - return FeedbackStatus.Reject; - } - else if (delivery.State.DescriptorCode == AmqpConstants.ReleasedOutcome.DescriptorCode) - { - return FeedbackStatus.Abandon; - } - else - { - throw new InvalidOperationException($"Unknown disposition outcome - {delivery.State.DescriptorCode}"); - } - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.SendingLinkHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -155,11 +156,6 @@ public static void InvalidLinkState(SendingLinkHandler handler) Log.LogWarning((int)EventIds.InvalidLinkState, $"Cannot send messages when {handler.Type} link state is {handler.Link.State} for {handler.ClientId}"); } - internal static void ErrorProcessingMessage(Exception e, SendingLinkHandler handler) - { - Log.LogWarning((int)EventIds.ErrorProcessing, e, $"Error processing message in {handler.Type} link for {handler.ClientId}"); - } - public static void ErrorDisposingMessage(Exception e, SendingLinkHandler handler) { Log.LogWarning((int)EventIds.ErrorDisposing, e, $"Error disposing message in {handler.Type} link for {handler.ClientId}"); @@ -180,6 +176,11 @@ string GetMessageId() Log.LogDebug((int)EventIds.MessageSent, $"Sent message with id {GetMessageId()} to device {handler.ClientId}"); } + + internal static void ErrorProcessingMessage(Exception e, SendingLinkHandler handler) + { + Log.LogWarning((int)EventIds.ErrorProcessing, e, $"Error processing message in {handler.Type} link for {handler.ClientId}"); + } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinReceivingLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinReceivingLinkHandler.cs index 0698476260a..7b401af4e8d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinReceivingLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinReceivingLinkHandler.cs @@ -26,7 +26,8 @@ public TwinReceivingLinkHandler( IIdentity identity, IReceivingAmqpLink link, Uri requestUri, - IDictionary boundVariables, IConnectionHandler connectionHandler, + IDictionary boundVariables, + IConnectionHandler connectionHandler, IMessageConverter messageConverter) : base(identity, link, requestUri, boundVariables, connectionHandler, messageConverter) { @@ -34,11 +35,11 @@ public TwinReceivingLinkHandler( public override LinkType Type => LinkType.TwinReceiving; - protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; - public override string CorrelationId => AmqpConnectionUtils.GetCorrelationId(this.Link); + protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; + protected override async Task OnMessageReceived(AmqpMessage amqpMessage) { if (!amqpMessage.MessageAnnotations.Map.TryGetValue("operation", out string operation) || @@ -98,8 +99,8 @@ protected override async Task OnMessageReceived(AmqpMessage amqpMessage) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = AmqpEventIds.TwinReceivingLinkHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinSendingLinkHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinSendingLinkHandler.cs index 34949259ef5..90a7f790f11 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinSendingLinkHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/linkhandlers/TwinSendingLinkHandler.cs @@ -27,13 +27,13 @@ public TwinSendingLinkHandler( { } - protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; - public override LinkType Type => LinkType.TwinSending; public override string CorrelationId => AmqpConnectionUtils.GetCorrelationId(this.Link); + protected override QualityOfService QualityOfService => QualityOfService.AtMostOnce; + protected override async Task OnOpenAsync(TimeSpan timeout) { await base.OnOpenAsync(timeout); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/AmqpSettingsProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/AmqpSettingsProvider.cs index e52bf5f74a2..4ff4693826a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/AmqpSettingsProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/AmqpSettingsProvider.cs @@ -52,7 +52,7 @@ void AddSaslProvider() // needs (i.e. old clients that are still using EXTERNAL for CBS). // saslProvider.AddHandler(new SaslExternalHandler()); - // CBS + // CBS saslProvider.AddHandler(new SaslAnonymousHandler()); // CBS - used by some SDKs like C diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/DefaultTransportSettings.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/DefaultTransportSettings.cs index 41ffcf1f6a0..e2e12ce39c3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/DefaultTransportSettings.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Amqp/settings/DefaultTransportSettings.cs @@ -49,6 +49,7 @@ public DefaultTransportSettings( { tlsSettings.CertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; } + this.Settings = tlsSettings; } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientProvider.cs index a9642aa096e..cd8fd8574a7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientProvider.cs @@ -26,6 +26,7 @@ public IClient Create(IIdentity identity, IAuthenticationMethod authenticationMe DeviceClient deviceClient = DeviceClient.Create(identity.IotHubHostName, authenticationMethod, transportSettings); return new DeviceClientWrapper(deviceClient); } + throw new InvalidOperationException($"Invalid client identity type {identity.GetType()}"); } @@ -45,6 +46,7 @@ public IClient Create(IIdentity identity, string connectionString, ITransportSet DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(connectionString, transportSettings); return new DeviceClientWrapper(deviceClient); } + throw new InvalidOperationException($"Invalid client identity type {identity.GetType()}"); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientTokenCloudConnection.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientTokenCloudConnection.cs index 2d6ea31a863..20bd49955b3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientTokenCloudConnection.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ClientTokenCloudConnection.cs @@ -49,6 +49,8 @@ class ClientTokenCloudConnection : CloudConnection, IClientTokenCloudConnection { } + protected override bool CallbacksEnabled => this.callbacksEnabled; + public static async Task Create( ITokenCredentials tokenCredentials, Action connectionStatusChangedHandler, @@ -81,11 +83,11 @@ public static async Task Create( /// This method does the following - /// 1. Updates the Identity to be used for the cloud connection /// 2. Updates the cloud proxy - - /// i. If there is an existing device client and + /// i. If there is an existing device client and /// a. If is waiting for an updated token, and the Identity has a token, /// then it uses that to give it to the waiting client authentication method. /// b. If not, then it creates a new cloud proxy (and device client) and closes the existing one - /// ii. Else, if there is no cloud proxy, then opens a device client and creates a cloud proxy. + /// ii. Else, if there is no cloud proxy, then opens a device client and creates a cloud proxy. /// public async Task UpdateTokenAsync(ITokenCredentials newTokenCredentials) { @@ -123,10 +125,10 @@ public async Task UpdateTokenAsync(ITokenCredentials newTokenCreden }); return cp; } - // Else this is a new connection for the same device Id. So open a new connection, - // and if that is successful, close the existing one. else { + // Else this is a new connection for the same device Id. So open a new connection, + // and if that is successful, close the existing one. ICloudProxy newCloudProxy = await this.CreateNewCloudProxyAsync(tokenProvider); await cp.CloseAsync(); return newCloudProxy; @@ -155,10 +157,22 @@ public async Task UpdateTokenAsync(ITokenCredentials newTokenCreden } } - protected override bool CallbacksEnabled => this.callbacksEnabled; - protected override Option GetCloudProxy() => this.cloudProxy; + // Checks if the token expires too soon + static bool IsTokenUsable(string hostname, string token) + { + try + { + return TokenHelper.GetTokenExpiryTimeRemaining(hostname, token) > TokenExpiryBuffer; + } + catch (Exception e) + { + Events.ErrorCheckingTokenUsable(e); + return false; + } + } + /// /// If the existing Identity has a usable token, then use it. /// Else, generate a notification of token being near expiry and return a task that @@ -224,20 +238,6 @@ async Task GetNewToken(string currentToken) } } - // Checks if the token expires too soon - static bool IsTokenUsable(string hostname, string token) - { - try - { - return TokenHelper.GetTokenExpiryTimeRemaining(hostname, token) > TokenExpiryBuffer; - } - catch (Exception e) - { - Events.ErrorCheckingTokenUsable(e); - return false; - } - } - class ClientTokenBasedTokenProvider : ITokenProvider { readonly ClientTokenCloudConnection cloudConnection; @@ -270,8 +270,8 @@ public async Task GetTokenAsync(Option ttl) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.CloudConnection; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -283,6 +283,17 @@ enum EventIds ErrorCheckingTokenUsability } + public static void ErrorCheckingTokenUsable(Exception ex) + { + Log.LogDebug((int)EventIds.ErrorCheckingTokenUsability, ex, "Error checking if token is usable."); + } + + public static void TokenNotUsable(IIdentity identity, string newToken) + { + TimeSpan timeRemaining = TokenHelper.GetTokenExpiryTimeRemaining(identity.IotHubHostName, newToken); + Log.LogDebug((int)EventIds.ObtainedNewToken, Invariant($"Token received for client {identity.Id} expires in {timeRemaining}, and so is not usable. Getting a fresh token...")); + } + internal static void GetNewToken(string id) { Log.LogDebug((int)EventIds.CreateNewToken, Invariant($"Getting new token for {id}.")); @@ -318,17 +329,6 @@ internal static void ErrorRenewingToken(Exception ex) { Log.LogDebug((int)EventIds.ErrorRenewingToken, ex, "Critical Error trying to renew Token."); } - - public static void ErrorCheckingTokenUsable(Exception ex) - { - Log.LogDebug((int)EventIds.ErrorCheckingTokenUsability, ex, "Error checking if token is usable."); - } - - public static void TokenNotUsable(IIdentity identity, string newToken) - { - TimeSpan timeRemaining = TokenHelper.GetTokenExpiryTimeRemaining(identity.IotHubHostName, newToken); - Log.LogDebug((int)EventIds.ObtainedNewToken, Invariant($"Token received for client {identity.Id} expires in {timeRemaining}, and so is not usable. Getting a fresh token...")); - } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnection.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnection.cs index a166fe54e27..109dde21656 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnection.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnection.cs @@ -48,6 +48,18 @@ protected CloudConnection( this.operationTimeout = operationTimeout; } + public Option CloudProxy => this.GetCloudProxy().Filter(cp => cp.IsActive); + + public bool IsActive => this.GetCloudProxy() + .Map(cp => cp.IsActive) + .GetOrElse(false); + + protected IIdentity Identity { get; } + + protected Action ConnectionStatusChangedHandler { get; } + + protected virtual bool CallbacksEnabled { get; } = true; + public static async Task Create( IIdentity identity, Action connectionStatusChangedHandler, @@ -76,18 +88,8 @@ public static async Task Create( return cloudConnection; } - public Option CloudProxy => this.GetCloudProxy().Filter(cp => cp.IsActive); - - public bool IsActive => this.GetCloudProxy() - .Map(cp => cp.IsActive) - .GetOrElse(false); - public Task CloseAsync() => this.GetCloudProxy().Map(cp => cp.CloseAsync()).GetOrElse(Task.FromResult(false)); - protected IIdentity Identity { get; } - - protected Action ConnectionStatusChangedHandler { get; } - protected virtual Option GetCloudProxy() => this.cloudProxy; protected async Task CreateNewCloudProxyAsync(ITokenProvider newTokenProvider) @@ -104,8 +106,6 @@ protected async Task CreateNewCloudProxyAsync(ITokenProvider newTok return proxy; } - protected virtual bool CallbacksEnabled { get; } = true; - async Task ConnectToIoTHub(ITokenProvider newTokenProvider) { Events.AttemptingConnectionWithTransport(this.transportSettingsList, this.Identity); @@ -114,11 +114,13 @@ async Task ConnectToIoTHub(ITokenProvider newTokenProvider) client.SetOperationTimeoutInMilliseconds((uint)this.operationTimeout.TotalMilliseconds); client.SetConnectionStatusChangedHandler(this.InternalConnectionStatusChangesHandler); - // TODO: Add support for ProductInfo - //if (!string.IsNullOrWhiteSpace(newCredentials.ProductInfo)) - //{ - // client.SetProductInfo(newCredentials.ProductInfo); - //} + // TODO: Add support for ProductInfo + /* + if (!string.IsNullOrWhiteSpace(newCredentials.ProductInfo)) + { + client.SetProductInfo(newCredentials.ProductInfo); + } + */ await client.OpenAsync(); Events.CreateDeviceClientSuccess(this.transportSettingsList, this.operationTimeout, this.Identity); @@ -149,8 +151,8 @@ void InternalConnectionStatusChangesHandler(ConnectionStatus status, ConnectionS static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.CloudConnection; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -158,6 +160,22 @@ enum EventIds TransportConnected } + public static void AttemptingConnectionWithTransport(ITransportSettings[] transportSettings, IIdentity identity) + { + string transportType = transportSettings.Length == 1 + ? TransportName(transportSettings[0].GetTransportType()) + : transportSettings.Select(t => TransportName(t.GetTransportType())).Join("/"); + Log.LogInformation((int)EventIds.AttemptingTransport, $"Attempting to connect to IoT Hub for client {identity.Id} via {transportType}..."); + } + + 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.TotalSeconds} seconds."); + } + static string TransportName(TransportType type) { switch (type) @@ -174,22 +192,6 @@ static string TransportName(TransportType type) return type.ToString(); } } - - public static void AttemptingConnectionWithTransport(ITransportSettings[] transportSettings, IIdentity identity) - { - string transportType = transportSettings.Length == 1 - ? TransportName(transportSettings[0].GetTransportType()) - : transportSettings.Select(t => TransportName(t.GetTransportType())).Join("/"); - Log.LogInformation((int)EventIds.AttemptingTransport, $"Attempting to connect to IoT Hub for client {identity.Id} via {transportType}..."); - } - - 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.TotalSeconds} seconds."); - } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnectionProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnectionProvider.cs index 57e0f013ef1..b61706ff2fb 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnectionProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudConnectionProvider.cs @@ -38,7 +38,8 @@ public class CloudConnectionProvider : ICloudConnectionProvider readonly TimeSpan operationTimeout; Option edgeHub; - public CloudConnectionProvider(IMessageConverterProvider messageConverterProvider, + public CloudConnectionProvider( + IMessageConverterProvider messageConverterProvider, int connectionPoolSize, IClientProvider clientProvider, Option upstreamProtocol, @@ -69,55 +70,6 @@ public void BindEdgeHub(IEdgeHub edgeHubInstance) this.edgeHub = Option.Some(Preconditions.CheckNotNull(edgeHubInstance, nameof(edgeHubInstance))); } - internal static ITransportSettings[] GetTransportSettings(Option upstreamProtocol, int connectionPoolSize) - { - return upstreamProtocol - .Map( - up => - { - TransportType transportType = UpstreamProtocolTransportTypeMap[up]; - switch (transportType) - { - case TransportType.Amqp_Tcp_Only: - case TransportType.Amqp_WebSocket_Only: - return new ITransportSettings[] - { - new AmqpTransportSettings(transportType) - { - AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings - { - Pooling = true, - MaxPoolSize = (uint)connectionPoolSize, - ConnectionIdleTimeout = MinAmqpConnectionMuxIdleTimeout - } - } - }; - - case TransportType.Mqtt_Tcp_Only: - case TransportType.Mqtt_WebSocket_Only: - return new ITransportSettings[] - { - new MqttTransportSettings(transportType) - }; - - default: - throw new ArgumentException($"Unsupported transport type {up}"); - } - }) - .GetOrElse( - () => new ITransportSettings[] { - new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) - { - AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings - { - Pooling = true, - MaxPoolSize = (uint)connectionPoolSize, - ConnectionIdleTimeout = MinAmqpConnectionMuxIdleTimeout - } - } - }); - } - public async Task> Connect(IClientCredentials clientCredentials, Action connectionStatusChangedHandler) { Preconditions.CheckNotNull(clientCredentials, nameof(clientCredentials)); @@ -180,23 +132,24 @@ public async Task> Connect(IIdentity identity, Action serviceIdentity = (await this.deviceScopeIdentitiesCache.GetServiceIdentity(identity.Id)) .Filter(s => s.Status == ServiceIdentityStatus.Enabled); return await serviceIdentity - .Map(async si => - { - Events.CreatingCloudConnectionOnBehalfOf(identity); - ICloudConnection cc = await CloudConnection.Create( - identity, - connectionStatusChangedHandler, - this.transportSettings, - this.messageConverterProvider, - this.clientProvider, - cloudListener, - this.edgeHubTokenProvider, - this.idleTimeout, - this.closeOnIdleTimeout, - this.operationTimeout); - Events.SuccessCreatingCloudConnection(identity); - return Try.Success(cc); - }) + .Map( + async si => + { + Events.CreatingCloudConnectionOnBehalfOf(identity); + ICloudConnection cc = await CloudConnection.Create( + identity, + connectionStatusChangedHandler, + this.transportSettings, + this.messageConverterProvider, + this.clientProvider, + cloudListener, + this.edgeHubTokenProvider, + this.idleTimeout, + this.closeOnIdleTimeout, + this.operationTimeout); + Events.SuccessCreatingCloudConnection(identity); + return Try.Success(cc); + }) .GetOrElse( async () => { @@ -214,10 +167,60 @@ public async Task> Connect(IIdentity identity, Action upstreamProtocol, int connectionPoolSize) + { + return upstreamProtocol + .Map( + up => + { + TransportType transportType = UpstreamProtocolTransportTypeMap[up]; + switch (transportType) + { + case TransportType.Amqp_Tcp_Only: + case TransportType.Amqp_WebSocket_Only: + return new ITransportSettings[] + { + new AmqpTransportSettings(transportType) + { + AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings + { + Pooling = true, + MaxPoolSize = (uint)connectionPoolSize, + ConnectionIdleTimeout = MinAmqpConnectionMuxIdleTimeout + } + } + }; + + case TransportType.Mqtt_Tcp_Only: + case TransportType.Mqtt_WebSocket_Only: + return new ITransportSettings[] + { + new MqttTransportSettings(transportType) + }; + + default: + throw new ArgumentException($"Unsupported transport type {up}"); + } + }) + .GetOrElse( + () => new ITransportSettings[] + { + new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) + { + AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings + { + Pooling = true, + MaxPoolSize = (uint)connectionPoolSize, + ConnectionIdleTimeout = MinAmqpConnectionMuxIdleTimeout + } + } + }); + } + static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.CloudConnectionProvider; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxy.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxy.cs index 014afd6c99d..12a4a55efe5 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxy.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxy.cs @@ -28,7 +28,8 @@ class CloudProxy : ICloudProxy readonly ResettableTimer timer; readonly bool closeOnIdleTimeout; - public CloudProxy(IClient client, + public CloudProxy( + IClient client, IMessageConverterProvider messageConverterProvider, string clientId, Action connectionStatusChangedHandler, @@ -47,18 +48,8 @@ public CloudProxy(IClient client, { this.connectionStatusChangedHandler = connectionStatusChangedHandler; } - Events.Initialized(this); - } - Task HandleIdleTimeout() - { - if (this.closeOnIdleTimeout) - { - Events.TimedOutClosing(this); - return this.CloseAsync(); - } - - return Task.CompletedTask; + Events.Initialized(this); } public bool IsActive => this.client.IsActive; @@ -237,6 +228,17 @@ public void StartListening() // This API is to be used for Tests only. internal CloudReceiver GetCloudReceiver() => this.cloudReceiver; + Task HandleIdleTimeout() + { + if (this.closeOnIdleTimeout) + { + Events.TimedOutClosing(this); + return this.CloseAsync(); + } + + return Task.CompletedTask; + } + bool EnsureCloudReceiver(string operation) { if (this.cloudReceiver == null) @@ -244,6 +246,7 @@ bool EnsureCloudReceiver(string operation) Events.CloudReceiverNull(this.clientId, operation); return false; } + this.timer.Disable(); return true; } @@ -266,6 +269,7 @@ Task HandleException(Exception ex) { Events.ExceptionInHandleException(this, ex, e); } + return Task.CompletedTask; } @@ -280,6 +284,7 @@ internal class CloudReceiver // IotHub has max timeout set to 5 minutes, add 30 seconds to make sure it doesn't timeout before IotHub static readonly TimeSpan DeviceMethodMaxResponseTimeout = TimeSpan.FromSeconds(5 * 60 + 30); + // Timeout for receive message because the default timeout is too long (4 minutes) for the case when the connection is closed static readonly TimeSpan ReceiveC2DMessageTimeout = TimeSpan.FromSeconds(20); @@ -289,7 +294,6 @@ public CloudReceiver(CloudProxy cloudProxy, ICloudListener cloudListener) this.cloudListener = Preconditions.CheckNotNull(cloudListener, nameof(cloudListener)); IMessageConverter converter = cloudProxy.messageConverterProvider.Get(); this.desiredUpdateHandler = new DesiredPropertyUpdateHandler(cloudListener, converter, cloudProxy); - } public void StartListening() @@ -305,7 +309,43 @@ public void StartListening() } } } + } + public Task CloseAsync() + { + Events.Closing(this.cloudProxy); + this.cancellationTokenSource.Cancel(); + return this.receiveMessageTask.GetOrElse(Task.CompletedTask); + } + + public Task SetupCallMethodAsync() => this.cloudProxy.client.SetMethodDefaultHandlerAsync(this.MethodCallHandler, null); + + public Task RemoveCallMethodAsync() => this.cloudProxy.client.SetMethodDefaultHandlerAsync(null, null); + + public Task SetupDesiredPropertyUpdatesAsync() => this.cloudProxy.client.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyUpdates, this.desiredUpdateHandler); + + public Task RemoveDesiredPropertyUpdatesAsync() + { + // return this.deviceClient.SetDesiredPropertyUpdateCallback(null, null); + // TODO: update device SDK to unregister callback for desired properties by passing null + return Task.CompletedTask; + } + + internal async Task MethodCallHandler(MethodRequest methodrequest, object usercontext) + { + Preconditions.CheckNotNull(methodrequest, nameof(methodrequest)); + + Events.MethodCallReceived(this.cloudProxy.clientId); + var direceMethodRequest = new DirectMethodRequest(this.cloudProxy.clientId, methodrequest.Name, methodrequest.Data, DeviceMethodMaxResponseTimeout); + DirectMethodResponse directMethodResponse = await this.cloudListener.CallMethodAsync(direceMethodRequest); + MethodResponse methodResponse = directMethodResponse.Data == null ? new MethodResponse(directMethodResponse.Status) : new MethodResponse(directMethodResponse.Data, directMethodResponse.Status); + return methodResponse; + } + + static Task OnDesiredPropertyUpdates(TwinCollection desiredProperties, object userContext) + { + var handler = (DesiredPropertyUpdateHandler)userContext; + return handler.OnDesiredPropertyUpdates(desiredProperties); } async Task C2DMessagesLoop(IClient deviceClient) @@ -339,6 +379,7 @@ async Task C2DMessagesLoop(IClient deviceClient) Events.ErrorReceivingMessage(this.cloudProxy, e); } } + Events.ReceiverStopped(this.cloudProxy); } catch (Exception ex) @@ -353,43 +394,6 @@ async Task C2DMessagesLoop(IClient deviceClient) } } - public Task CloseAsync() - { - Events.Closing(this.cloudProxy); - this.cancellationTokenSource.Cancel(); - return this.receiveMessageTask.GetOrElse(Task.CompletedTask); - } - - public Task SetupCallMethodAsync() => this.cloudProxy.client.SetMethodDefaultHandlerAsync(this.MethodCallHandler, null); - - public Task RemoveCallMethodAsync() => this.cloudProxy.client.SetMethodDefaultHandlerAsync(null, null); - - internal async Task MethodCallHandler(MethodRequest methodrequest, object usercontext) - { - Preconditions.CheckNotNull(methodrequest, nameof(methodrequest)); - - Events.MethodCallReceived(this.cloudProxy.clientId); - var direceMethodRequest = new Core.DirectMethodRequest(this.cloudProxy.clientId, methodrequest.Name, methodrequest.Data, DeviceMethodMaxResponseTimeout); - DirectMethodResponse directMethodResponse = await this.cloudListener.CallMethodAsync(direceMethodRequest); - MethodResponse methodResponse = directMethodResponse.Data == null ? new MethodResponse(directMethodResponse.Status) : new MethodResponse(directMethodResponse.Data, directMethodResponse.Status); - return methodResponse; - } - - public Task SetupDesiredPropertyUpdatesAsync() => this.cloudProxy.client.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyUpdates, this.desiredUpdateHandler); - - public Task RemoveDesiredPropertyUpdatesAsync() - { - // return this.deviceClient.SetDesiredPropertyUpdateCallback(null, null); - // TODO: update device SDK to unregister callback for desired properties by passing null - return Task.CompletedTask; - } - - static Task OnDesiredPropertyUpdates(TwinCollection desiredProperties, object userContext) - { - var handler = (DesiredPropertyUpdateHandler)userContext; - return handler.OnDesiredPropertyUpdates(desiredProperties); - } - class DesiredPropertyUpdateHandler { readonly ICloudListener listener; @@ -487,11 +491,6 @@ public static void SendFeedbackMessage(CloudProxy cloudProxy) Log.LogDebug((int)EventIds.SendFeedbackMessage, Invariant($"Sending feedback message for device {cloudProxy.clientId}")); } - internal static void ExceptionInHandleException(CloudProxy cloudProxy, Exception handlingException, Exception caughtException) - { - Log.LogDebug((int)EventIds.ExceptionInHandleException, Invariant($"Cloud proxy {cloudProxy.id} got exception {caughtException} while handling exception {handlingException}")); - } - public static void MessageReceived(string clientId) { Log.LogDebug((int)EventIds.MessageReceived, Invariant($"Received message from cloud for device {clientId}")); @@ -522,16 +521,6 @@ public static void StartListening(string clientId) Log.LogInformation((int)EventIds.StartListening, Invariant($"Start listening for C2D messages for device {clientId}")); } - internal static void TerminatingErrorReceivingMessage(CloudProxy cloudProxy, Exception e) - { - Log.LogInformation((int)EventIds.ReceiveError, e, Invariant($"Error receiving C2D messages for device {cloudProxy.clientId} in cloud proxy {cloudProxy.id}. Closing receive loop.")); - } - - internal static void CloudReceiverNull(string clientId, string operation) - { - Log.LogWarning((int)EventIds.CloudReceiverNull, Invariant($"Cannot complete operation {operation} for device {clientId} because cloud receiver is null")); - } - public static void ErrorOpening(string clientId, Exception ex) { Log.LogWarning((int)EventIds.ErrorOpening, ex, Invariant($"Error opening IotHub connection for device {clientId}")); @@ -571,6 +560,21 @@ public static void HandleNre(CloudProxy cloudProxy) { Log.LogDebug((int)EventIds.HandleNre, Invariant($"Got a NullReferenceException from client for {cloudProxy.clientId}. Closing the cloud proxy since it may be in a bad state.")); } + + internal static void ExceptionInHandleException(CloudProxy cloudProxy, Exception handlingException, Exception caughtException) + { + Log.LogDebug((int)EventIds.ExceptionInHandleException, Invariant($"Cloud proxy {cloudProxy.id} got exception {caughtException} while handling exception {handlingException}")); + } + + internal static void TerminatingErrorReceivingMessage(CloudProxy cloudProxy, Exception e) + { + Log.LogInformation((int)EventIds.ReceiveError, e, Invariant($"Error receiving C2D messages for device {cloudProxy.clientId} in cloud proxy {cloudProxy.id}. Closing receive loop.")); + } + + internal static void CloudReceiverNull(string clientId, string operation) + { + Log.LogWarning((int)EventIds.CloudReceiverNull, Invariant($"Cannot complete operation {operation} for device {clientId} because cloud receiver is null")); + } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxyEventIds.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxyEventIds.cs index 3d8da4e8772..04fd7be18d6 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxyEventIds.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/CloudProxyEventIds.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy { public static class CloudProxyEventIds { - const int EventIdStart = 3000; public const int CloudProxy = EventIdStart; public const int CloudReceiver = EventIdStart + 100; public const int CloudConnectionProvider = EventIdStart + 200; @@ -15,5 +14,6 @@ public static class CloudProxyEventIds public const int DeviceScopeApiClient = EventIdStart + 800; public const int CloudTokenAuthenticator = EventIdStart + 900; public const int CertificateCredentialsAuthenticator = EventIdStart + 1000; + const int EventIdStart = 3000; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClient.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClient.cs index 1a733f4d4f6..d3fca6ba7ad 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClient.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClient.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy using Microsoft.Extensions.Logging; /// - /// This implementation of IClient wraps an underlying IClient, and + /// This implementation of IClient wraps an underlying IClient, and /// reports success/failures of the IoTHub calls to IDeviceConnectivityManager. /// It also receives connectivity callbacks from IDeviceConnectivityManager /// and translates them to the ConnectionStatusChangedHandler. @@ -36,24 +36,6 @@ public ConnectivityAwareClient(IClient client, IDeviceConnectivityManager device this.deviceConnectivityManager.DeviceDisconnected += this.HandleDeviceDisconnectedEvent; } - void HandleDeviceConnectedEvent(object sender, EventArgs eventArgs) - { - if (!this.isConnected.GetAndSet(true)) - { - Events.ChangingStatus(this.isConnected, this.identity); - this.connectionStatusChangedHandler?.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok); - } - } - - void HandleDeviceDisconnectedEvent(object sender, EventArgs eventArgs) - { - if (this.isConnected.GetAndSet(false)) - { - Events.ChangingStatus(this.isConnected, this.identity); - this.connectionStatusChangedHandler?.Invoke(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.No_Network); - } - } - public bool IsActive => this.underlyingClient.IsActive; public async Task CloseAsync() @@ -79,9 +61,9 @@ public Task RejectAsync(string messageId) => public Task OpenAsync() => this.InvokeFunc(() => this.underlyingClient.OpenAsync(), nameof(this.OpenAsync)); - public Task SendEventAsync(Client.Message message) => this.InvokeFunc(() => this.underlyingClient.SendEventAsync(message), nameof(this.SendEventAsync)); + public Task SendEventAsync(Message message) => this.InvokeFunc(() => this.underlyingClient.SendEventAsync(message), nameof(this.SendEventAsync)); - public Task SendEventBatchAsync(IEnumerable messages) => + public Task SendEventBatchAsync(IEnumerable messages) => this.InvokeFunc(() => this.underlyingClient.SendEventBatchAsync(messages), nameof(this.SendEventBatchAsync)); public void SetConnectionStatusChangedHandler(ConnectionStatusChangesHandler handler) @@ -91,7 +73,7 @@ public void SetConnectionStatusChangedHandler(ConnectionStatusChangesHandler han } // The SDK caches whether DesiredProperty Update callback has been set and returns directly in that case. - // So this method is not a good candidate for checking connectivity status. + // So this method is not a good candidate for checking connectivity status. public Task SetDesiredPropertyUpdateCallbackAsync(DesiredPropertyUpdateCallback onDesiredPropertyUpdates, object userContext) => this.InvokeFunc(() => this.underlyingClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertyUpdates, userContext), nameof(this.SetDesiredPropertyUpdateCallbackAsync), false); @@ -110,19 +92,39 @@ public void Dispose() this.underlyingClient?.Dispose(); } + void HandleDeviceConnectedEvent(object sender, EventArgs eventArgs) + { + if (!this.isConnected.GetAndSet(true)) + { + Events.ChangingStatus(this.isConnected, this.identity); + this.connectionStatusChangedHandler?.Invoke(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok); + } + } + + void HandleDeviceDisconnectedEvent(object sender, EventArgs eventArgs) + { + if (this.isConnected.GetAndSet(false)) + { + Events.ChangingStatus(this.isConnected, this.identity); + this.connectionStatusChangedHandler?.Invoke(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.No_Network); + } + } + void InternalConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusChangeReason reason) { Events.ReceivedDeviceSdkCallback(this.identity, status, reason); // @TODO: Ignore callback from Device SDK since it seems to be generating a lot of spurious Connected/NotConnected callbacks - //if (status == ConnectionStatus.Connected) - //{ - // this.deviceConnectivityManager.CallSucceeded(); - //} - //else - //{ - // this.deviceConnectivityManager.CallTimedOut(); - //} - //this.connectionStatusChangedHandler?.Invoke(status, reason); + /* + if (status == ConnectionStatus.Connected) + { + this.deviceConnectivityManager.CallSucceeded(); + } + else + { + this.deviceConnectivityManager.CallTimedOut(); + } + this.connectionStatusChangedHandler?.Invoke(status, reason); + */ } async Task InvokeFunc(Func> func, string operation, bool useForConnectivityCheck = true) @@ -177,8 +179,8 @@ Task InvokeFunc(Func func, string operation, bool useForConnectivityCheck static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.ConnectivityAwareClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClientProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClientProvider.cs index a2faed9c049..b8a7b1747bf 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClientProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ConnectivityAwareClientProvider.cs @@ -26,6 +26,7 @@ public IClient Create(IIdentity identity, string connectionString, ITransportSet public async Task CreateAsync(IIdentity identity, ITransportSettings[] transportSettings) => new ConnectivityAwareClient(await this.underlyingClientProvider.CreateAsync(identity, transportSettings).ConfigureAwait(false), this.deviceConnectivityManager, identity); + public IClient Create(IIdentity identity, ITokenProvider tokenProvider, ITransportSettings[] transportSettings) => new ConnectivityAwareClient(this.underlyingClientProvider.Create(identity, tokenProvider, transportSettings), this.deviceConnectivityManager, identity); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceClientWrapper.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceClientWrapper.cs index 0df5d194615..8cf87e8e53c 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceClientWrapper.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceClientWrapper.cs @@ -51,7 +51,6 @@ public async Task OpenAsync() } } - public Task ReceiveAsync(TimeSpan receiveMessageTimeout) => this.underlyingDeviceClient.ReceiveAsync(receiveMessageTimeout); public Task RejectAsync(string messageId) => this.underlyingDeviceClient.RejectAsync(messageId); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceConnectivityManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceConnectivityManager.cs index 7c3703daae4..e56031512bf 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceConnectivityManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceConnectivityManager.cs @@ -1,67 +1,52 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy { + using System; + using System.Text; + using System.Threading.Tasks; + using System.Timers; 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 Microsoft.Azure.Devices.Edge.Util.Concurrency; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Stateless; - using System; - using System.Text; - using System.Threading.Tasks; - using System.Timers; - using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using static System.FormattableString; /// /// This class checks for the connectivity of the Edge device and raises - /// events if the device is disconnected, or gets re-connected. - /// It does this by relying on success / failure of the various IotHub operations + /// events if the device is disconnected, or gets re-connected. + /// It does this by relying on success / failure of the various IotHub operations /// performed on behalf of the connected clients and even edge hub. - /// If there is no activity, then it makes a dummy IoThub call periodically to check if the + /// If there is no activity, then it makes a dummy IoThub call periodically to check if the /// device is connected. - /// - /// This class maintains a state machine with the following logic - + /// + /// This class maintains a state machine with the following logic - /// - If an Iothub call succeeds, the device is in a connected state - /// - If a call fails, it goes to the Trying state, and tries to make another Iothub call. + /// - If a call fails, it goes to the Trying state, and tries to make another Iothub call. /// - If this call also fails, it goes into the disconnected state (else goes back to connected state) - /// - In disconnected state, it tries to connect to IotHub periodically. If a call succeeds, then + /// - In disconnected state, it tries to connect to IotHub periodically. If a call succeeds, then /// then it goes back to connected state /// public class DeviceConnectivityManager : IDeviceConnectivityManager { - enum State - { - Connected, - Trying, - Disconnected, - } - - enum Trigger - { - CallSucceeded, - CallTimedOut - } - - State state; - ConnectivityChecker connectivityChecker; readonly StateMachine machine; readonly Timer connectedTimer; readonly Timer disconnectedTimer; readonly IIdentity testClientIdentity; - public event EventHandler DeviceConnected; - public event EventHandler DeviceDisconnected; + State state; + ConnectivityChecker connectivityChecker; public DeviceConnectivityManager( TimeSpan minConnectivityCheckFrequency, TimeSpan disconnectedCheckFrequency, IIdentity testClientIdentity) { - this.testClientIdentity = Preconditions.CheckNotNull(testClientIdentity, nameof(testClientIdentity)); + this.testClientIdentity = Preconditions.CheckNotNull(testClientIdentity, nameof(testClientIdentity)); this.connectedTimer = new Timer(minConnectivityCheckFrequency.TotalMilliseconds); this.disconnectedTimer = new Timer(disconnectedCheckFrequency.TotalMilliseconds); this.machine = new StateMachine(() => this.state, s => this.state = s); @@ -91,23 +76,28 @@ public DeviceConnectivityManager( Events.Created(minConnectivityCheckFrequency, disconnectedCheckFrequency); } - public void SetConnectionManager(IConnectionManager connectionManager) + public event EventHandler DeviceConnected; + + public event EventHandler DeviceDisconnected; + + enum State { - this.connectivityChecker = new ConnectivityChecker(connectionManager, this.testClientIdentity); - this.connectedTimer.Start(); - Events.SetConnectionManager(); + Connected, + Trying, + Disconnected, } - void ResetDisconnectedTimer() + enum Trigger { - this.disconnectedTimer.Stop(); - this.disconnectedTimer.Start(); + CallSucceeded, + CallTimedOut } - void ResetConnectedTimer() + public void SetConnectionManager(IConnectionManager connectionManager) { - this.connectedTimer.Stop(); + this.connectivityChecker = new ConnectivityChecker(connectionManager, this.testClientIdentity); this.connectedTimer.Start(); + Events.SetConnectionManager(); } public void CallSucceeded() @@ -122,9 +112,21 @@ public void CallTimedOut() this.machine.Fire(Trigger.CallTimedOut); } + void ResetDisconnectedTimer() + { + this.disconnectedTimer.Stop(); + this.disconnectedTimer.Start(); + } + + void ResetConnectedTimer() + { + this.connectedTimer.Stop(); + this.connectedTimer.Start(); + } + void OnConnected() { - Events.OnConnected(); + Events.OnConnected(); this.connectedTimer.Start(); } @@ -175,6 +177,7 @@ class ConnectivityChecker readonly IConnectionManager connectionManager; readonly IIdentity testClientIdentity; readonly AsyncLock sync = new AsyncLock(); + readonly Lazy testMessage = new Lazy( () => { @@ -202,8 +205,8 @@ public async Task Check() static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.DeviceConnectivityManager; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -220,16 +223,6 @@ enum EventIds MakingTestIotHubCall } - internal static void SetConnectionManager() - { - Log.LogDebug((int)EventIds.SetConnectionManager, Invariant($"ConnectionManager provided")); - } - - internal static void Created(TimeSpan connectedCheckFrequency, TimeSpan disconnectedCheckFrequency) - { - Log.LogDebug((int)EventIds.Created, Invariant($"Created DeviceConnectivityManager with connected check frequency {connectedCheckFrequency} and disconnected check frequency {disconnectedCheckFrequency}")); - } - public static void OnDisconnectedExit() { Log.LogInformation((int)EventIds.OnDisconnectedExit, Invariant($"Exiting disconnected state")); @@ -274,6 +267,16 @@ public static void MakingTestIotHubCall() { Log.LogDebug((int)EventIds.MakingTestIotHubCall, Invariant($"Calling IotHub to test connectivity")); } + + internal static void SetConnectionManager() + { + Log.LogDebug((int)EventIds.SetConnectionManager, Invariant($"ConnectionManager provided")); + } + + internal static void Created(TimeSpan connectedCheckFrequency, TimeSpan disconnectedCheckFrequency) + { + Log.LogDebug((int)EventIds.Created, Invariant($"Created DeviceConnectivityManager with connected check frequency {connectedCheckFrequency} and disconnected check frequency {disconnectedCheckFrequency}")); + } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceScopeApiClient.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceScopeApiClient.cs index fe5c0e62901..c9e92e18931 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceScopeApiClient.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/DeviceScopeApiClient.cs @@ -15,20 +15,25 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy public class DeviceScopeApiClient : IDeviceScopeApiClient { const int RetryCount = 2; - static readonly ITransientErrorDetectionStrategy TransientErrorDetectionStrategy = new ErrorDetectionStrategy(); - static readonly RetryStrategy TransientRetryStrategy = - new ExponentialBackoff(RetryCount, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(4)); const string InScopeIdentitiesUriTemplate = "/devices/{0}/modules/{1}/devicesAndModulesInDeviceScope?deviceCount={2}&continuationToken={3}&api-version={4}"; + const string InScopeTargetIdentityUriFormat = "/devices/{0}/modules/{1}/deviceAndModuleInDeviceScope?targetDeviceId={2}&targetModuleId={3}&api-version={4}"; + const string ApiVersion = "2018-08-30-preview"; + + static readonly ITransientErrorDetectionStrategy TransientErrorDetectionStrategy = new ErrorDetectionStrategy(); + + static readonly RetryStrategy TransientRetryStrategy = + new ExponentialBackoff(RetryCount, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(4)); + readonly RetryStrategy retryStrategy; + readonly Uri iotHubBaseHttpUri; readonly string deviceId; readonly string moduleId; readonly int batchSize; readonly ITokenProvider edgeHubTokenProvider; - const string ApiVersion = "2018-08-30-preview"; public DeviceScopeApiClient( string iotHubHostName, @@ -77,6 +82,13 @@ internal Uri GetServiceUri(string targetDeviceId, string targetModuleId) return uri; } + static Task ExecuteWithRetry(Func> func, Action onRetry, RetryStrategy retryStrategy) + { + var transientRetryPolicy = new RetryPolicy(TransientErrorDetectionStrategy, retryStrategy); + transientRetryPolicy.Retrying += (_, args) => onRetry(args); + return transientRetryPolicy.ExecuteAsync(func); + } + async Task GetIdentitiesInScopeWithRetry(Uri uri) { try @@ -116,13 +128,6 @@ async Task GetIdentitiesInScope(Uri uri) } } - static Task ExecuteWithRetry(Func> func, Action onRetry, RetryStrategy retryStrategy) - { - var transientRetryPolicy = new RetryPolicy(TransientErrorDetectionStrategy, retryStrategy); - transientRetryPolicy.Retrying += (_, args) => onRetry(args); - return transientRetryPolicy.ExecuteAsync(func); - } - internal class ErrorDetectionStrategy : ITransientErrorDetectionStrategy { static readonly ISet NonTransientExceptions = new HashSet @@ -161,8 +166,8 @@ public bool IsTransient(Exception ex) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.DeviceScopeApiClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.csproj b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.csproj index 44994fca890..2f95590326c 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.csproj @@ -29,4 +29,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ModuleClientWrapper.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ModuleClientWrapper.cs index d6e70d4b010..36fbc3c8770 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ModuleClientWrapper.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ModuleClientWrapper.cs @@ -47,7 +47,7 @@ public async Task OpenAsync() { await this.underlyingModuleClient.OpenAsync(); } - catch(Exception) + catch (Exception) { this.isActive.Set(false); throw; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceIdentityHelpers.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceIdentityHelpers.cs index fe16756faab..a948d67c479 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceIdentityHelpers.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceIdentityHelpers.cs @@ -74,6 +74,7 @@ public static IEnumerable ToServiceCapabilities(this DeviceCapabilities { serviceCapabilities.Add(Constants.IotEdgeIdentityCapability); } + return serviceCapabilities; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceProxy.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceProxy.cs index 128bfc18611..6823e6b96cc 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceProxy.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/ServiceProxy.cs @@ -37,36 +37,39 @@ public async Task> GetServiceIdentity(string deviceId) Option serviceIdentityResult = scopeResult - .Map(sc => - { - if (sc.Devices != null) + .Map( + sc => { - int count = sc.Devices.Count(); - if (count == 1) + if (sc.Devices != null) { - ServiceIdentity serviceIdentity = sc.Devices.First().ToServiceIdentity(); - return Option.Some(serviceIdentity); + int count = sc.Devices.Count(); + if (count == 1) + { + ServiceIdentity serviceIdentity = sc.Devices.First().ToServiceIdentity(); + return Option.Some(serviceIdentity); + } + else + { + Events.UnexpectedResult(count, 1, "devices", deviceId); + } } else { - Events.UnexpectedResult(count, 1, "devices", deviceId); + Events.NullDevicesResult(deviceId); } - } - else + + return Option.None(); + }) + .GetOrElse( + () => { - Events.NullDevicesResult(deviceId); - } - return Option.None(); - }) - .GetOrElse(() => - { - Events.NullResult(deviceId); - return Option.None(); - }); + Events.NullResult(deviceId); + return Option.None(); + }); return serviceIdentityResult; } - + public async Task> GetServiceIdentity(string deviceId, string moduleId) { string id = $"{deviceId}/{moduleId}"; @@ -104,6 +107,7 @@ public async Task> GetServiceIdentity(string deviceId, s { Events.NullDevicesResult(id); } + return Option.None(); }) .GetOrElse( @@ -116,65 +120,10 @@ public async Task> GetServiceIdentity(string deviceId, s return serviceIdentityResult; } - class ServiceIdentitiesIterator : IServiceIdentitiesIterator - { - readonly IDeviceScopeApiClient securityScopesApiClient; - Option continuationLink = Option.None(); - - public ServiceIdentitiesIterator(IDeviceScopeApiClient securityScopesApiClient) - { - this.securityScopesApiClient = Preconditions.CheckNotNull(securityScopesApiClient, nameof(securityScopesApiClient)); - this.HasNext = true; - Events.IteratorCreated(); - } - - public async Task> GetNext() - { - if (!this.HasNext) - { - return Enumerable.Empty(); - } - - var serviceIdentities = new List(); - ScopeResult scopeResult = await this.continuationLink.Map(c => this.securityScopesApiClient.GetNext(c)) - .GetOrElse(() => this.securityScopesApiClient.GetIdentitiesInScope()); - if (scopeResult == null) - { - Events.NullResult(); - } - else - { - Events.ScopeResultReceived(scopeResult); - if (scopeResult.Devices != null) - { - serviceIdentities.AddRange(scopeResult.Devices.Select(d => d.ToServiceIdentity())); - } - - if (scopeResult.Modules != null) - { - serviceIdentities.AddRange(scopeResult.Modules.Select(m => m.ToServiceIdentity())); - } - - if (!string.IsNullOrWhiteSpace(scopeResult.ContinuationLink)) - { - this.continuationLink = Option.Some(scopeResult.ContinuationLink); - this.HasNext = true; - } - else - { - this.HasNext = false; - } - } - return serviceIdentities; - } - - public bool HasNext { get; private set; } - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.ServiceProxy; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -224,5 +173,61 @@ public static void BadRequestResult(string id, HttpStatusCode statusCode) Log.LogDebug((int)EventIds.ScopeResultReceived, $"Received scope result for {id} with status code {statusCode} indicating that {id} has been removed from the scope"); } } + + class ServiceIdentitiesIterator : IServiceIdentitiesIterator + { + readonly IDeviceScopeApiClient securityScopesApiClient; + Option continuationLink = Option.None(); + + public ServiceIdentitiesIterator(IDeviceScopeApiClient securityScopesApiClient) + { + this.securityScopesApiClient = Preconditions.CheckNotNull(securityScopesApiClient, nameof(securityScopesApiClient)); + this.HasNext = true; + Events.IteratorCreated(); + } + + public bool HasNext { get; private set; } + + public async Task> GetNext() + { + if (!this.HasNext) + { + return Enumerable.Empty(); + } + + var serviceIdentities = new List(); + ScopeResult scopeResult = await this.continuationLink.Map(c => this.securityScopesApiClient.GetNext(c)) + .GetOrElse(() => this.securityScopesApiClient.GetIdentitiesInScope()); + if (scopeResult == null) + { + Events.NullResult(); + } + else + { + Events.ScopeResultReceived(scopeResult); + if (scopeResult.Devices != null) + { + serviceIdentities.AddRange(scopeResult.Devices.Select(d => d.ToServiceIdentity())); + } + + if (scopeResult.Modules != null) + { + serviceIdentities.AddRange(scopeResult.Modules.Select(m => m.ToServiceIdentity())); + } + + if (!string.IsNullOrWhiteSpace(scopeResult.ContinuationLink)) + { + this.continuationLink = Option.Some(scopeResult.ContinuationLink); + this.HasNext = true; + } + else + { + this.HasNext = false; + } + } + + return serviceIdentities; + } + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinCollectionMessageConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinCollectionMessageConverter.cs index b8b3068845e..0e116d329d0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinCollectionMessageConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinCollectionMessageConverter.cs @@ -14,11 +14,12 @@ public IMessage ToMessage(TwinCollection sourceMessage) { byte[] body = Encoding.UTF8.GetBytes(sourceMessage.ToJson()); return new EdgeMessage.Builder(body) - .SetSystemProperties(new Dictionary - { - [SystemProperties.EnqueuedTime] = DateTime.UtcNow.ToString("o"), - [SystemProperties.Version] = sourceMessage.Version.ToString() - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.EnqueuedTime] = DateTime.UtcNow.ToString("o"), + [SystemProperties.Version] = sourceMessage.Version.ToString() + }) .Build(); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinMessageConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinMessageConverter.cs index a31f72f6f9c..546463ba541 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinMessageConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/TwinMessageConverter.cs @@ -50,6 +50,7 @@ public Twin FromMessage(IMessage message) { twin.Version = version; } + return twin; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/CloudTokenAuthenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/CloudTokenAuthenticator.cs index 7674fe54392..db154d03058 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/CloudTokenAuthenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/CloudTokenAuthenticator.cs @@ -74,8 +74,8 @@ bool TryGetSharedAccessSignature(string token, IIdentity identity, out SharedAcc static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.CloudTokenAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/DeviceScopeTokenAuthenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/DeviceScopeTokenAuthenticator.cs index f8eb056e9fc..5afd88ad1ff 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/DeviceScopeTokenAuthenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/DeviceScopeTokenAuthenticator.cs @@ -24,8 +24,7 @@ public DeviceScopeTokenAuthenticator( IAuthenticator underlyingAuthenticator, bool allowDeviceAuthForModule, bool syncServiceIdentityOnFailure) - : - base(deviceScopeIdentitiesCache, underlyingAuthenticator, allowDeviceAuthForModule, syncServiceIdentityOnFailure) + : base(deviceScopeIdentitiesCache, underlyingAuthenticator, allowDeviceAuthForModule, syncServiceIdentityOnFailure) { this.iothubHostName = Preconditions.CheckNonWhiteSpace(iothubHostName, nameof(iothubHostName)); this.edgeHubHostName = Preconditions.CheckNotNull(edgeHubHostName, nameof(edgeHubHostName)); @@ -38,6 +37,67 @@ protected override bool ValidateWithServiceIdentity(ServiceIdentity serviceIdent ? this.ValidateCredentials(sharedAccessSignature, serviceIdentity, credentials.Identity) : false; + internal bool ValidateAudience(string audience, IIdentity identity) + { + Preconditions.CheckNonWhiteSpace(audience, nameof(audience)); + audience = WebUtility.UrlDecode(audience.Trim()); + // The audience should be in one of the following formats - + // {HostName}/devices/{deviceId}/modules/{moduleId} + // {HostName}/devices/{deviceId} + string[] parts = audience.Split('/'); + string hostName; + if (parts.Length == 3) + { + hostName = parts[0]; + string deviceId = parts[2]; + if (identity is IDeviceIdentity deviceIdentity && deviceIdentity.DeviceId != deviceId) + { + Events.IdMismatch(audience, identity, deviceIdentity.DeviceId); + return false; + } + else if (identity is IModuleIdentity moduleIdentity && moduleIdentity.DeviceId != deviceId) + { + Events.IdMismatch(audience, identity, moduleIdentity.DeviceId); + return false; + } + } + else if (parts.Length == 5) + { + hostName = parts[0]; + string deviceId = parts[2]; + string moduleId = parts[4]; + if (!(identity is IModuleIdentity moduleIdentity)) + { + Events.InvalidAudience(audience, identity); + return false; + } + else if (moduleIdentity.DeviceId != deviceId) + { + Events.IdMismatch(audience, identity, moduleIdentity.DeviceId); + return false; + } + else if (moduleIdentity.ModuleId != moduleId) + { + Events.IdMismatch(audience, identity, moduleIdentity.ModuleId); + return false; + } + } + else + { + Events.InvalidAudience(audience, identity); + return false; + } + + if (string.IsNullOrWhiteSpace(hostName) || + !(this.iothubHostName.Equals(hostName) || this.edgeHubHostName.Equals(hostName))) + { + Events.InvalidHostName(identity.Id, hostName, this.iothubHostName, this.edgeHubHostName); + return false; + } + + return true; + } + bool TryGetSharedAccessSignature(string token, IIdentity identity, out SharedAccessSignature sharedAccessSignature) { try @@ -106,71 +166,10 @@ bool ValidateTokenWithSecurityIdentity(SharedAccessSignature sharedAccessSignatu .GetOrElse(() => throw new InvalidOperationException($"Unable to validate token because the service identity has empty symmetric keys")); } - internal bool ValidateAudience(string audience, IIdentity identity) - { - Preconditions.CheckNonWhiteSpace(audience, nameof(audience)); - audience = WebUtility.UrlDecode(audience.Trim()); - // The audience should be in one of the following formats - - // {HostName}/devices/{deviceId}/modules/{moduleId} - // {HostName}/devices/{deviceId} - string[] parts = audience.Split('/'); - string hostName; - if (parts.Length == 3) - { - hostName = parts[0]; - string deviceId = parts[2]; - if (identity is IDeviceIdentity deviceIdentity && deviceIdentity.DeviceId != deviceId) - { - Events.IdMismatch(audience, identity, deviceIdentity.DeviceId); - return false; - } - else if (identity is IModuleIdentity moduleIdentity && moduleIdentity.DeviceId != deviceId) - { - Events.IdMismatch(audience, identity, moduleIdentity.DeviceId); - return false; - } - } - else if (parts.Length == 5) - { - hostName = parts[0]; - string deviceId = parts[2]; - string moduleId = parts[4]; - if (!(identity is IModuleIdentity moduleIdentity)) - { - Events.InvalidAudience(audience, identity); - return false; - } - else if (moduleIdentity.DeviceId != deviceId) - { - Events.IdMismatch(audience, identity, moduleIdentity.DeviceId); - return false; - } - else if (moduleIdentity.ModuleId != moduleId) - { - Events.IdMismatch(audience, identity, moduleIdentity.ModuleId); - return false; - } - } - else - { - Events.InvalidAudience(audience, identity); - return false; - } - - if (string.IsNullOrWhiteSpace(hostName) || - !(this.iothubHostName.Equals(hostName) || this.edgeHubHostName.Equals(hostName))) - { - Events.InvalidHostName(identity.Id, hostName, this.iothubHostName, this.edgeHubHostName); - return false; - } - - return true; - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.TokenCredentialsAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/TokenCacheAuthenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/TokenCacheAuthenticator.cs index 26b947c059a..109c3f63c6f 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/TokenCacheAuthenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.CloudProxy/authenticators/TokenCacheAuthenticator.cs @@ -37,7 +37,8 @@ async Task AuthenticateAsync(IClientCredentials clientCredentials, bool re Option validatedCredentials = await this.credentialsCache.Get(tokenCredentials.Identity); bool isAuthenticated = await validatedCredentials.Map( - v => Task.FromResult(v is ITokenCredentials validatedTokenCredentials && + v => Task.FromResult( + v is ITokenCredentials validatedTokenCredentials && this.IsValid(clientCredentials, validatedTokenCredentials.Token) && validatedTokenCredentials.Token.Equals(tokenCredentials.Token))) .GetOrElse(Task.FromResult(false)); @@ -78,8 +79,8 @@ bool IsValid(IClientCredentials clientCredentials, string token) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = CloudProxyEventIds.TokenCredentialsAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Authenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Authenticator.cs index dad12fba409..dc153b15434 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Authenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Authenticator.cs @@ -62,8 +62,8 @@ async Task AuthenticateAsync(IClientCredentials clientCredentials, bool re static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.Authenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionManager.cs index e2144604b61..182d669e1fa 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionManager.cs @@ -27,11 +27,6 @@ public class ConnectionManager : IConnectionManager readonly ICredentialsCache credentialsCache; readonly IIdentityProvider identityProvider; - public event EventHandler CloudConnectionLost; - public event EventHandler CloudConnectionEstablished; - public event EventHandler DeviceConnected; - public event EventHandler DeviceDisconnected; - public ConnectionManager( ICloudConnectionProvider cloudConnectionProvider, ICredentialsCache credentialsCache, @@ -45,6 +40,14 @@ public ConnectionManager( Util.Metrics.RegisterGaugeCallback(() => Metrics.SetConnectedClientCountGauge(this)); } + public event EventHandler CloudConnectionEstablished; + + public event EventHandler CloudConnectionLost; + + public event EventHandler DeviceConnected; + + public event EventHandler DeviceDisconnected; + public IEnumerable GetConnectedClients() => this.devices.Values .Where(d => d.DeviceConnection.Map(dc => dc.IsActive).GetOrElse(false)) @@ -70,21 +73,6 @@ public Task RemoveDeviceConnection(string id) : Task.CompletedTask; } - async Task RemoveDeviceConnection(ConnectedDevice device, bool removeCloudConnection) - { - await device.DeviceConnection.Filter(dp => dp.IsActive) - .ForEachAsync(dp => dp.CloseAsync(new EdgeHubConnectionException($"Connection closed for device {device.Identity.Id}."))); - - if (removeCloudConnection) - { - await device.CloudConnection.Filter(cp => cp.IsActive) - .ForEachAsync(cp => cp.CloseAsync()); - } - - Events.RemoveDeviceConnection(device.Identity.Id); - this.DeviceDisconnected?.Invoke(this, device.Identity); - } - public Option GetDeviceConnection(string id) { return this.devices.TryGetValue(Preconditions.CheckNonWhiteSpace(id, nameof(id)), out ConnectedDevice device) @@ -111,6 +99,7 @@ public void AddSubscription(string id, DeviceSubscription deviceSubscription) { throw new ArgumentException($"A connection for {id} not found."); } + device.DeviceConnection.Filter(d => d.IsActive) .ForEach(d => d.Subscriptions[deviceSubscription] = true); } @@ -121,15 +110,16 @@ public void RemoveSubscription(string id, DeviceSubscription deviceSubscription) { throw new ArgumentException($"A connection for {id} not found."); } + device.DeviceConnection.Filter(d => d.IsActive) .ForEach(d => d.Subscriptions[deviceSubscription] = false); } public Option> GetSubscriptions(string id) => this.devices.TryGetValue(Preconditions.CheckNonWhiteSpace(id, nameof(id)), out ConnectedDevice device) - ? device.DeviceConnection.Filter(d => d.IsActive) - .Map(d => new ReadOnlyDictionary(d.Subscriptions) as IReadOnlyDictionary) - : Option.None>(); + ? device.DeviceConnection.Filter(d => d.IsActive) + .Map(d => new ReadOnlyDictionary(d.Subscriptions) as IReadOnlyDictionary) + : Option.None>(); public async Task> CreateCloudConnectionAsync(IClientCredentials credentials) { @@ -143,7 +133,7 @@ public async Task> CreateCloudConnectionAsync(IClientCredential } // This method is not used, but it has important logic and this will be useful for offline scenarios. - // So do not delete this method. + // So do not delete this method. public async Task> GetOrCreateCloudConnectionAsync(IClientCredentials credentials) { Preconditions.CheckNotNull(credentials, nameof(credentials)); @@ -158,35 +148,55 @@ public async Task> GetOrCreateCloudConnectionAsync(IClientCrede return cloudProxyTry; } + static Try GetCloudProxyFromCloudConnection(Try cloudConnection, IIdentity identity) => cloudConnection.Success + ? cloudConnection.Value.CloudProxy.Map(cp => Try.Success(cp)) + .GetOrElse(() => Try.Failure(new EdgeHubConnectionException($"Unable to get cloud proxy for device {identity.Id}"))) + : Try.Failure(cloudConnection.Exception); + + async Task RemoveDeviceConnection(ConnectedDevice device, bool removeCloudConnection) + { + await device.DeviceConnection.Filter(dp => dp.IsActive) + .ForEachAsync(dp => dp.CloseAsync(new EdgeHubConnectionException($"Connection closed for device {device.Identity.Id}."))); + + if (removeCloudConnection) + { + await device.CloudConnection.Filter(cp => cp.IsActive) + .ForEachAsync(cp => cp.CloseAsync()); + } + + Events.RemoveDeviceConnection(device.Identity.Id); + this.DeviceDisconnected?.Invoke(this, device.Identity); + } + Task> CreateOrUpdateCloudConnection(ConnectedDevice device, IClientCredentials credentials) => device.CloudConnection.Map( - async c => - { - try + async c => { - if (!(credentials is ITokenCredentials tokenCredentials)) - { - throw new InvalidOperationException($"Cannot update credentials of type {credentials.AuthenticationType} for {credentials.Identity.Id}"); - } - else if (!(c is IClientTokenCloudConnection clientTokenCloudConnection)) + try { - throw new InvalidOperationException($"Cannot update token for an existing cloud connection that is not based on client token for {credentials.Identity.Id}"); + if (!(credentials is ITokenCredentials tokenCredentials)) + { + throw new InvalidOperationException($"Cannot update credentials of type {credentials.AuthenticationType} for {credentials.Identity.Id}"); + } + else if (!(c is IClientTokenCloudConnection clientTokenCloudConnection)) + { + throw new InvalidOperationException($"Cannot update token for an existing cloud connection that is not based on client token for {credentials.Identity.Id}"); + } + else + { + await clientTokenCloudConnection.UpdateTokenAsync(tokenCredentials); + return Try.Success(c); + } } - else + catch (Exception ex) { - await clientTokenCloudConnection.UpdateTokenAsync(tokenCredentials); - return Try.Success(c); + return Try.Failure(new EdgeHubConnectionException($"Error updating identity for device {device.Identity.Id}", ex)); } - } - catch (Exception ex) - { - return Try.Failure(new EdgeHubConnectionException($"Error updating identity for device {device.Identity.Id}", ex)); - } - }) - .GetOrElse(() => this.cloudConnectionProvider.Connect(credentials, (identity, status) => this.CloudConnectionStatusChangedHandler(identity, status))); - + }) + .GetOrElse(() => this.cloudConnectionProvider.Connect(credentials, (identity, status) => this.CloudConnectionStatusChangedHandler(identity, status))); - async void CloudConnectionStatusChangedHandler(string deviceId, + async void CloudConnectionStatusChangedHandler( + string deviceId, CloudConnectionStatus connectionStatus) { Preconditions.CheckNonWhiteSpace(deviceId, nameof(deviceId)); @@ -226,6 +236,7 @@ await clientCredentials.ForEachAsync( await this.RemoveDeviceConnection(device, true); this.CloudConnectionLost?.Invoke(this, device.Identity); } + break; case CloudConnectionStatus.DisconnectedTokenExpired: @@ -237,11 +248,12 @@ await clientCredentials.ForEachAsync( case CloudConnectionStatus.Disconnected: Events.InvokingCloudConnectionLostEvent(device.Identity); this.CloudConnectionLost?.Invoke(this, device.Identity); - await device.CloudConnection.Filter(cp => cp.IsActive).ForEachAsync(cp => - { - Events.CloudConnectionLostClosingClient(device.Identity); - return cp.CloseAsync(); - }); + await device.CloudConnection.Filter(cp => cp.IsActive).ForEachAsync( + cp => + { + Events.CloudConnectionLostClosingClient(device.Identity); + return cp.CloseAsync(); + }); break; case CloudConnectionStatus.ConnectionEstablished: @@ -263,9 +275,10 @@ ConnectedDevice CreateOrUpdateConnectedDevice(IIdentity identity) { string deviceId = Preconditions.CheckNotNull(identity, nameof(identity)).Id; Preconditions.CheckNonWhiteSpace(deviceId, nameof(deviceId)); - return this.devices.AddOrUpdate(deviceId, - id => this.CreateNewConnectedDevice(identity), - (id, cd) => new ConnectedDevice(identity, cd.CloudConnection, cd.DeviceConnection)); + return this.devices.AddOrUpdate( + deviceId, + id => this.CreateNewConnectedDevice(identity), + (id, cd) => new ConnectedDevice(identity, cd.CloudConnection, cd.DeviceConnection)); } ConnectedDevice CreateNewConnectedDevice(IIdentity identity) @@ -276,15 +289,11 @@ ConnectedDevice CreateNewConnectedDevice(IIdentity identity) { throw new EdgeHubConnectionException($"Edge hub already has maximum allowed clients ({this.maxClients - 1}) connected."); } + return new ConnectedDevice(identity); } } - static Try GetCloudProxyFromCloudConnection(Try cloudConnection, IIdentity identity) => cloudConnection.Success - ? cloudConnection.Value.CloudProxy.Map(cp => Try.Success(cp)) - .GetOrElse(() => Try.Failure(new EdgeHubConnectionException($"Unable to get cloud proxy for device {identity.Id}"))) - : Try.Failure(cloudConnection.Exception); - class ConnectedDevice { // Device Proxy methods are sync coming from the Protocol gateway, @@ -337,6 +346,7 @@ public async Task> CreateOrUpdateCloudConnection( { this.CloudConnection = Option.Some(newCloudConnection.Value); } + return newCloudConnection; } } @@ -348,32 +358,34 @@ public async Task> GetOrCreateCloudConnection( return await this.CloudConnection.Filter(cp => cp.IsActive) .Map(c => Task.FromResult(Try.Success(c))) - .GetOrElse(async () => - { - return await this.cloudConnectionCreateTask.Filter(c => !c.IsCompleted) - .GetOrElse( - async () => - { - using (await this.cloudConnectionLock.LockAsync()) + .GetOrElse( + async () => + { + return await this.cloudConnectionCreateTask.Filter(c => !c.IsCompleted) + .GetOrElse( + async () => { - return await this.CloudConnection.Filter(cp => cp.IsActive) - .Map(c => Task.FromResult(Try.Success(c))) - .GetOrElse(async () => - { - return await this.cloudConnectionCreateTask.Filter(c => !c.IsCompleted) - .GetOrElse( - async () => - { - Task> createTask = cloudConnectionCreator(this); - this.cloudConnectionCreateTask = Option.Some(createTask); - Try cloudConnectionResult = await createTask; - this.CloudConnection = cloudConnectionResult.Ok(); - return cloudConnectionResult; - }); - }); - } - }); - }); + using (await this.cloudConnectionLock.LockAsync()) + { + return await this.CloudConnection.Filter(cp => cp.IsActive) + .Map(c => Task.FromResult(Try.Success(c))) + .GetOrElse( + async () => + { + return await this.cloudConnectionCreateTask.Filter(c => !c.IsCompleted) + .GetOrElse( + async () => + { + Task> createTask = cloudConnectionCreator(this); + this.cloudConnectionCreateTask = Option.Some(createTask); + Try cloudConnectionResult = await createTask; + this.CloudConnection = cloudConnectionResult.Ok(); + return cloudConnectionResult; + }); + }); + } + }); + }); } } @@ -394,26 +406,10 @@ public DeviceConnection(IDeviceProxy deviceProxy, IDictionary this.DeviceProxy.CloseAsync(ex); } - static class Metrics - { - static readonly GaugeOptions ConnectedClientGaugeOptions = new GaugeOptions - { - Name = "EdgeHubConnectedClientGauge", - MeasurementUnit = Unit.Events - }; - - public static void SetConnectedClientCountGauge(ConnectionManager connectionManager) - { - // Subtract EdgeHub from the list of connected clients - int connectedClients = connectionManager.GetConnectedClients().Count() - 1; - Util.Metrics.SetGauge(ConnectedClientGaugeOptions, connectedClients); - } - }; - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.ConnectionManager; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -452,18 +448,6 @@ public static void RemoveDeviceConnection(string id) Log.LogInformation((int)EventIds.RemoveDeviceConnection, Invariant($"Device connection removed for device {id}")); } - internal static void GetCloudConnection(IIdentity identity, Try cloudConnection) - { - if (cloudConnection.Success) - { - Log.LogDebug((int)EventIds.ObtainedCloudConnection, Invariant($"Obtained cloud connection for device {identity.Id}")); - } - else - { - Log.LogInformation((int)EventIds.ObtainCloudConnectionError, cloudConnection.Exception, Invariant($"Error getting cloud connection for device {identity.Id}")); - } - } - public static void ProcessingTokenNearExpiryEvent(IIdentity identity) { Log.LogDebug((int)EventIds.ProcessingTokenNearExpiryEvent, Invariant($"Processing token near expiry for {identity.Id}")); @@ -488,6 +472,34 @@ public static void CloudConnectionLostClosingClient(IIdentity identity) { Log.LogDebug((int)EventIds.CloudConnectionLostClosingClient, Invariant($"Cloud connection lost for {identity.Id}, closing client.")); } + + internal static void GetCloudConnection(IIdentity identity, Try cloudConnection) + { + if (cloudConnection.Success) + { + Log.LogDebug((int)EventIds.ObtainedCloudConnection, Invariant($"Obtained cloud connection for device {identity.Id}")); + } + else + { + Log.LogInformation((int)EventIds.ObtainCloudConnectionError, cloudConnection.Exception, Invariant($"Error getting cloud connection for device {identity.Id}")); + } + } + } + + static class Metrics + { + static readonly GaugeOptions ConnectedClientGaugeOptions = new GaugeOptions + { + Name = "EdgeHubConnectedClientGauge", + MeasurementUnit = Unit.Events + }; + + public static void SetConnectedClientCountGauge(ConnectionManager connectionManager) + { + // Subtract EdgeHub from the list of connected clients + int connectedClients = connectionManager.GetConnectedClients().Count() - 1; + Util.Metrics.SetGauge(ConnectedClientGaugeOptions, connectedClients); + } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionProvider.cs index fce6795c387..022ad6ec90a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionProvider.cs @@ -23,6 +23,8 @@ public Task GetDeviceListenerAsync(IIdentity identity) return Task.FromResult(deviceListener); } + public void Dispose() => this.Dispose(true); + protected virtual void Dispose(bool disposing) { if (disposing) @@ -30,7 +32,5 @@ protected virtual void Dispose(bool disposing) this.edgeHub?.Dispose(); } } - - public void Dispose() => this.Dispose(true); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionReauthenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionReauthenticator.cs index 9e1412a9fbe..926e0f32ae1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionReauthenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ConnectionReauthenticator.cs @@ -11,7 +11,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core using Microsoft.Azure.Devices.Edge.Hub.Core.Identity.Service; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; - using Timer = System.Timers.Timer; public sealed class ConnectionReauthenticator : IDisposable { @@ -43,18 +42,20 @@ public ConnectionReauthenticator( this.deviceScopeIdentitiesCache.ServiceIdentityRemoved += this.HandleServiceIdentityRemove; } - void DeviceConnected(object sender, EventArgs args) - { - Events.EdgeHubConnectionReestablished(); - this.deviceScopeIdentitiesCache.InitiateCacheRefresh(); - } - public void Init() { Events.StartingReauthTimer(this.timer); this.timer.Start(); } + public void Dispose() => this.timer?.Dispose(); + + void DeviceConnected(object sender, EventArgs args) + { + Events.EdgeHubConnectionReestablished(); + this.deviceScopeIdentitiesCache.InitiateCacheRefresh(); + } + async void ReauthenticateConnections(object sender, ElapsedEventArgs elapsedEventArgs) { try @@ -169,8 +170,8 @@ async void HandleServiceIdentityRemove(object sender, string id) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.PeriodicConnectionAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -192,11 +193,6 @@ public static void ErrorReauthenticating(Exception ex) Log.LogWarning((int)EventIds.ErrorReauthenticating, ex, "Error re-authenticating connected clients."); } - internal static void ErrorReauthenticating(string id, Exception ex) - { - Log.LogWarning((int)EventIds.ErrorReauthenticating, ex, $"Error re-authenticating client {id}, closing the connection."); - } - public static void ClientCredentialsResult(IIdentity identity, bool result) { if (result) @@ -263,8 +259,11 @@ public static void NotReauthenticated(string id) { Log.LogInformation((int)EventIds.ServiceIdentityRemoved, $"Unable to re-authenticate {id}, dropping client connection."); } - } - public void Dispose() => this.timer?.Dispose(); + internal static void ErrorReauthenticating(string id, Exception ex) + { + Log.LogWarning((int)EventIds.ErrorReauthenticating, ex, $"Error re-authenticating client {id}, closing the connection."); + } + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeAuthenticator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeAuthenticator.cs index 6d3b70dcbb6..ab0549e1462 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeAuthenticator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeAuthenticator.cs @@ -117,8 +117,8 @@ public async Task ReauthenticateAsync(IClientCredentials clientCredentials static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger>(); const int IdStart = HubCoreEventIds.DeviceScopeAuthenticator; + static readonly ILogger Log = Logger.Factory.CreateLogger>(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeIdentitiesCache.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeIdentitiesCache.cs index 44412ba6cf5..15d2b767673 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeIdentitiesCache.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DeviceScopeIdentitiesCache.cs @@ -24,14 +24,12 @@ public sealed class DeviceScopeIdentitiesCache : IDeviceScopeIdentitiesCache readonly Timer refreshCacheTimer; readonly TimeSpan refreshRate; readonly AsyncAutoResetEvent refreshCacheSignal = new AsyncAutoResetEvent(); - - Task refreshCacheTask; readonly object refreshCacheLock = new object(); - public event EventHandler ServiceIdentityUpdated; - public event EventHandler ServiceIdentityRemoved; + Task refreshCacheTask; - DeviceScopeIdentitiesCache(IServiceProxy serviceProxy, + DeviceScopeIdentitiesCache( + IServiceProxy serviceProxy, IKeyValueStore encryptedStorage, IDictionary initialCache, TimeSpan refreshRate) @@ -43,6 +41,10 @@ public sealed class DeviceScopeIdentitiesCache : IDeviceScopeIdentitiesCache this.refreshCacheTimer = new Timer(this.RefreshCache, null, TimeSpan.Zero, refreshRate); } + public event EventHandler ServiceIdentityRemoved; + + public event EventHandler ServiceIdentityUpdated; + public static async Task Create( IServiceProxy serviceProxy, IKeyValueStore encryptedStorage, @@ -56,6 +58,83 @@ public static async Task Create( return deviceScopeIdentitiesCache; } + public void InitiateCacheRefresh() + { + Events.ReceivedRequestToRefreshCache(); + this.refreshCacheSignal.Set(); + } + + public async Task RefreshServiceIdentity(string id) + { + try + { + Events.RefreshingServiceIdentity(id); + Option serviceIdentity = await this.GetServiceIdentityFromService(id); + await serviceIdentity + .Map(s => this.HandleNewServiceIdentity(s)) + .GetOrElse(() => this.HandleNoServiceIdentity(id)); + } + catch (Exception e) + { + Events.ErrorRefreshingCache(e, id); + } + } + + public async Task RefreshServiceIdentities(IEnumerable ids) + { + List idList = Preconditions.CheckNotNull(ids, nameof(ids)).ToList(); + foreach (string id in idList) + { + await this.RefreshServiceIdentity(id); + } + } + + public async Task> GetServiceIdentity(string id, bool refreshIfNotExists = false) + { + Preconditions.CheckNonWhiteSpace(id, nameof(id)); + Events.GettingServiceIdentity(id); + if (refreshIfNotExists && !this.serviceIdentityCache.ContainsKey(id)) + { + await this.RefreshServiceIdentity(id); + } + + return await this.GetServiceIdentityInternal(id); + } + + public void Dispose() + { + this.encryptedStore?.Dispose(); + this.refreshCacheTimer?.Dispose(); + this.refreshCacheTask?.Dispose(); + } + + internal Task> GetServiceIdentityFromService(string id) + { + // If it is a module id, it will have the format "deviceId/moduleId" + string[] parts = id.Split('/'); + if (parts.Length == 2) + { + return this.serviceProxy.GetServiceIdentity(parts[0], parts[1]); + } + else + { + return this.serviceProxy.GetServiceIdentity(id); + } + } + + static async Task> ReadCacheFromStore(IKeyValueStore encryptedStore) + { + IDictionary cache = new Dictionary(); + await encryptedStore.IterateBatch( + int.MaxValue, + (key, value) => + { + cache.Add(key, JsonConvert.DeserializeObject(value)); + return Task.CompletedTask; + }); + return cache; + } + void RefreshCache(object state) { lock (this.refreshCacheLock) @@ -68,12 +147,6 @@ void RefreshCache(object state) } } - public void InitiateCacheRefresh() - { - Events.ReceivedRequestToRefreshCache(); - this.refreshCacheSignal.Set(); - } - async Task RefreshCache() { while (true) @@ -132,43 +205,6 @@ async Task IsReady() } } - public async Task RefreshServiceIdentity(string id) - { - try - { - Events.RefreshingServiceIdentity(id); - Option serviceIdentity = await this.GetServiceIdentityFromService(id); - await serviceIdentity - .Map(s => this.HandleNewServiceIdentity(s)) - .GetOrElse(() => this.HandleNoServiceIdentity(id)); - } - catch (Exception e) - { - Events.ErrorRefreshingCache(e, id); - } - } - - public async Task RefreshServiceIdentities(IEnumerable ids) - { - List idList = Preconditions.CheckNotNull(ids, nameof(ids)).ToList(); - foreach (string id in idList) - { - await this.RefreshServiceIdentity(id); - } - } - - public async Task> GetServiceIdentity(string id, bool refreshIfNotExists = false) - { - Preconditions.CheckNonWhiteSpace(id, nameof(id)); - Events.GettingServiceIdentity(id); - if (refreshIfNotExists && !this.serviceIdentityCache.ContainsKey(id)) - { - await this.RefreshServiceIdentity(id); - } - - return await this.GetServiceIdentityInternal(id); - } - async Task> GetServiceIdentityInternal(string id) { Preconditions.CheckNonWhiteSpace(id, nameof(id)); @@ -198,9 +234,9 @@ async Task HandleNewServiceIdentity(ServiceIdentity serviceIdentity) using (await this.cacheLock.LockAsync()) { bool hasUpdated = this.serviceIdentityCache.TryGetValue(serviceIdentity.Id, out StoredServiceIdentity currentStoredServiceIdentity) - && currentStoredServiceIdentity.ServiceIdentity - .Map(s => !s.Equals(serviceIdentity)) - .GetOrElse(false); + && currentStoredServiceIdentity.ServiceIdentity + .Map(s => !s.Equals(serviceIdentity)) + .GetOrElse(false); var storedServiceIdentity = new StoredServiceIdentity(serviceIdentity); this.serviceIdentityCache[serviceIdentity.Id] = storedServiceIdentity; await this.SaveServiceIdentityToStore(serviceIdentity.Id, storedServiceIdentity); @@ -218,40 +254,6 @@ async Task SaveServiceIdentityToStore(string id, StoredServiceIdentity storedSer await this.encryptedStore.Put(id, serviceIdentityString); } - static async Task> ReadCacheFromStore(IKeyValueStore encryptedStore) - { - IDictionary cache = new Dictionary(); - await encryptedStore.IterateBatch( - int.MaxValue, - (key, value) => - { - cache.Add(key, JsonConvert.DeserializeObject(value)); - return Task.CompletedTask; - }); - return cache; - } - - internal Task> GetServiceIdentityFromService(string id) - { - // If it is a module id, it will have the format "deviceId/moduleId" - string[] parts = id.Split('/'); - if (parts.Length == 2) - { - return this.serviceProxy.GetServiceIdentity(parts[0], parts[1]); - } - else - { - return this.serviceProxy.GetServiceIdentity(id); - } - } - - public void Dispose() - { - this.encryptedStore?.Dispose(); - this.refreshCacheTimer?.Dispose(); - this.refreshCacheTask?.Dispose(); - } - internal class StoredServiceIdentity { public StoredServiceIdentity(ServiceIdentity serviceIdentity) @@ -261,7 +263,8 @@ public StoredServiceIdentity(ServiceIdentity serviceIdentity) public StoredServiceIdentity(string id) : this(Preconditions.CheckNotNull(id, nameof(id)), null, DateTime.UtcNow) - { } + { + } [JsonConstructor] StoredServiceIdentity(string id, ServiceIdentity serviceIdentity, DateTime timestamp) @@ -284,8 +287,8 @@ public StoredServiceIdentity(string id) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.DeviceScopeIdentitiesCache; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -303,9 +306,6 @@ enum EventIds GettingServiceIdentity } - internal static void InitializingRefreshTask(TimeSpan refreshRate) => - Log.LogDebug((int)EventIds.InitializingRefreshTask, $"Initializing device scope identities cache refresh task to run every {refreshRate.TotalMinutes} minutes."); - public static void Created() => Log.LogInformation((int)EventIds.Created, "Created device scope identities cache"); @@ -352,6 +352,9 @@ public static void GettingServiceIdentity(string id) => public static void RefreshingServiceIdentity(string id) => Log.LogDebug((int)EventIds.RefreshingServiceIdentity, $"Refreshing service identity for {id}"); + + internal static void InitializingRefreshTask(TimeSpan refreshRate) => + Log.LogDebug((int)EventIds.InitializingRefreshTask, $"Initializing device scope identities cache refresh task to run every {refreshRate.TotalMinutes} minutes."); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DirectMethodRequest.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DirectMethodRequest.cs index 5672273fb3b..8d4ec920733 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DirectMethodRequest.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/DirectMethodRequest.cs @@ -7,13 +7,14 @@ public class DirectMethodRequest { public DirectMethodRequest(string id, string name, byte[] data, TimeSpan responseTimeout) : this(id, name, data, responseTimeout, TimeSpan.Zero) - { } + { + } public DirectMethodRequest(string id, string name, byte[] data, TimeSpan responseTimeout, TimeSpan connectTimeout) { this.Id = id; this.Name = name; - this.Data = data; + this.Data = data; this.ConnectTimeout = connectTimeout; this.ResponseTimeout = responseTimeout; this.CorrelationId = Guid.NewGuid().ToString(); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnection.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnection.cs index bf840ae33ef..afe3415a3f7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnection.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnection.cs @@ -24,8 +24,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core /// public class EdgeHubConnection : IConfigSource { - Func configUpdateCallback; - Option lastDesiredProperties = Option.None(); readonly IIdentity edgeHubIdentity; readonly ITwinManager twinManager; readonly IMessageConverter twinCollectionMessageConverter; @@ -34,8 +32,11 @@ public class EdgeHubConnection : IConfigSource readonly RouteFactory routeFactory; readonly AsyncLock edgeHubConfigLock = new AsyncLock(); readonly IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache; + Func configUpdateCallback; + Option lastDesiredProperties = Option.None(); - internal EdgeHubConnection(IIdentity edgeHubIdentity, + internal EdgeHubConnection( + IIdentity edgeHubIdentity, ITwinManager twinManager, RouteFactory routeFactory, IMessageConverter twinCollectionMessageConverter, @@ -61,8 +62,7 @@ public static async Task Create( IMessageConverter twinCollectionMessageConverter, IMessageConverter twinMessageConverter, VersionInfo versionInfo, - IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache - ) + IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache) { Preconditions.CheckNotNull(edgeHubIdentity, nameof(edgeHubIdentity)); Preconditions.CheckNotNull(edgeHub, nameof(edgeHub)); @@ -79,8 +79,7 @@ IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache twinCollectionMessageConverter, twinMessageConverter, versionInfo ?? VersionInfo.Empty, - deviceScopeIdentitiesCache - ); + deviceScopeIdentitiesCache); await InitEdgeHub(edgeHubConnection, connectionManager, edgeHubIdentity, edgeHub); connectionManager.DeviceConnected += edgeHubConnection.DeviceConnected; @@ -89,6 +88,99 @@ IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache return edgeHubConnection; } + public async Task> GetConfig() + { + using (await this.edgeHubConfigLock.LockAsync()) + { + return await this.GetConfigInternal(); + } + } + + /// + /// If called multiple times, this will currently overwrite the existing callback + /// + public void SetConfigUpdatedCallback(Func callback) => + this.configUpdateCallback = Preconditions.CheckNotNull(callback, nameof(callback)); + + internal static void ValidateSchemaVersion(string schemaVersion) + { + if (string.IsNullOrWhiteSpace(schemaVersion) || !Version.TryParse(schemaVersion, out Version version)) + { + throw new ArgumentException($"Invalid desired properties schema version {schemaVersion ?? string.Empty}"); + } + + if (Constants.ConfigSchemaVersion.Major != version.Major) + { + throw new InvalidOperationException($"Desired properties schema version {schemaVersion} is not compatible with the expected version is {Constants.ConfigSchemaVersion}"); + } + + if (Constants.ConfigSchemaVersion.Minor != version.Minor) + { + Events.MismatchedMinorVersions(version, Constants.ConfigSchemaVersion); + } + } + + internal async void DeviceDisconnected(object sender, IIdentity device) + { + try + { + await this.UpdateDeviceConnectionStatus(device, ConnectionStatus.Disconnected); + } + catch (Exception ex) + { + Events.ErrorHandlingDeviceDisconnectedEvent(device, ex); + } + } + + internal async void DeviceConnected(object sender, IIdentity device) + { + try + { + await this.UpdateDeviceConnectionStatus(device, ConnectionStatus.Connected); + } + catch (Exception ex) + { + Events.ErrorHandlingDeviceConnectedEvent(device, ex); + } + } + + internal async Task HandleMethodInvocation(DirectMethodRequest request) + { + Preconditions.CheckNotNull(request, nameof(request)); + Events.MethodRequestReceived(request.Name); + if (request.Name.Equals(Constants.ServiceIdentityRefreshMethodName, StringComparison.OrdinalIgnoreCase)) + { + RefreshRequest refreshRequest; + try + { + refreshRequest = request.Data.FromBytes(); + } + catch (Exception e) + { + Events.ErrorParsingMethodRequest(e); + return new DirectMethodResponse(e, HttpStatusCode.BadRequest); + } + + try + { + Events.RefreshingServiceIdentities(refreshRequest.DeviceIds); + await this.deviceScopeIdentitiesCache.RefreshServiceIdentities(refreshRequest.DeviceIds); + Events.RefreshedServiceIdentities(refreshRequest.DeviceIds); + return new DirectMethodResponse(request.CorrelationId, null, (int)HttpStatusCode.OK); + } + catch (Exception e) + { + Events.ErrorRefreshingServiceIdentities(e); + return new DirectMethodResponse(e, HttpStatusCode.InternalServerError); + } + } + else + { + Events.InvalidMethodRequest(request.Name); + return new DirectMethodResponse(new InvalidOperationException($"Method {request.Name} is not supported"), HttpStatusCode.NotFound); + } + } + static Task InitEdgeHub(EdgeHubConnection edgeHubConnection, IConnectionManager connectionManager, IIdentity edgeHubIdentity, IEdgeHub edgeHub) { IDeviceProxy deviceProxy = new EdgeHubDeviceProxy(edgeHubConnection); @@ -99,14 +191,6 @@ static Task InitEdgeHub(EdgeHubConnection edgeHubConnection, IConnectionManager return Task.WhenAll(addDeviceConnectionTask, desiredPropertyUpdatesSubscriptionTask, methodsSubscriptionTask, clearDeviceConnectionStatusesTask); } - public async Task> GetConfig() - { - using (await this.edgeHubConfigLock.LockAsync()) - { - return await this.GetConfigInternal(); - } - } - // This method updates local state and should be called only after acquiring edgeHubConfigLock async Task> GetConfigInternal() { @@ -167,30 +251,6 @@ EdgeHubConfig GetEdgeHubConfig(DesiredProperties desiredProperties) return new EdgeHubConfig(desiredProperties.SchemaVersion, routes, desiredProperties.StoreAndForwardConfiguration); } - internal static void ValidateSchemaVersion(string schemaVersion) - { - if (string.IsNullOrWhiteSpace(schemaVersion) || !Version.TryParse(schemaVersion, out Version version)) - { - throw new ArgumentException($"Invalid desired properties schema version {schemaVersion ?? string.Empty}"); - } - - if (Constants.ConfigSchemaVersion.Major != version.Major) - { - throw new InvalidOperationException($"Desired properties schema version {schemaVersion} is not compatible with the expected version is {Constants.ConfigSchemaVersion}"); - } - - if (Constants.ConfigSchemaVersion.Minor != version.Minor) - { - Events.MismatchedMinorVersions(version, Constants.ConfigSchemaVersion); - } - } - - /// - /// If called multiple times, this will currently overwrite the existing callback - /// - public void SetConfigUpdatedCallback(Func callback) => - this.configUpdateCallback = Preconditions.CheckNotNull(callback, nameof(callback)); - async Task HandleDesiredPropertiesUpdate(IMessage desiredPropertiesUpdate) { try @@ -202,13 +262,14 @@ async Task HandleDesiredPropertiesUpdate(IMessage desiredPropertiesUpdate) .Map(e => this.PatchDesiredProperties(e, twinCollection)) .GetOrElse(() => this.GetConfigInternal()); - await edgeHubConfig.ForEachAsync(async config => - { - if (this.configUpdateCallback != null) + await edgeHubConfig.ForEachAsync( + async config => { - await this.configUpdateCallback(config); - } - }); + if (this.configUpdateCallback != null) + { + await this.configUpdateCallback(config); + } + }); } } catch (Exception ex) @@ -231,13 +292,13 @@ async Task> PatchDesiredProperties(TwinCollection baseline lastDesiredStatus = new LastDesiredStatus(200, string.Empty); Events.PatchConfigSuccess(); } - catch (Exception ex) { lastDesiredStatus = new LastDesiredStatus(400, $"Error while parsing desired properties - {ex.Message}"); edgeHubConfig = Option.None(); Events.ErrorPatchingDesiredProperties(ex); } + await this.UpdateReportedProperties(patch.Version, lastDesiredStatus); return edgeHubConfig; } @@ -258,30 +319,6 @@ Task UpdateReportedProperties(long desiredVersion, LastDesiredStatus desiredStat } } - internal async void DeviceDisconnected(object sender, IIdentity device) - { - try - { - await this.UpdateDeviceConnectionStatus(device, ConnectionStatus.Disconnected); - } - catch (Exception ex) - { - Events.ErrorHandlingDeviceDisconnectedEvent(device, ex); - } - } - - internal async void DeviceConnected(object sender, IIdentity device) - { - try - { - await this.UpdateDeviceConnectionStatus(device, ConnectionStatus.Connected); - } - catch (Exception ex) - { - Events.ErrorHandlingDeviceConnectedEvent(device, ex); - } - } - Task UpdateDeviceConnectionStatus(IIdentity client, ConnectionStatus connectionStatus) { try @@ -342,77 +379,26 @@ Task ClearDeviceConnectionStatuses() } } - internal async Task HandleMethodInvocation(DirectMethodRequest request) - { - Preconditions.CheckNotNull(request, nameof(request)); - Events.MethodRequestReceived(request.Name); - if (request.Name.Equals(Constants.ServiceIdentityRefreshMethodName, StringComparison.OrdinalIgnoreCase)) - { - RefreshRequest refreshRequest; - try - { - refreshRequest = request.Data.FromBytes(); - } - catch (Exception e) - { - Events.ErrorParsingMethodRequest(e); - return new DirectMethodResponse(e, HttpStatusCode.BadRequest); - } - - try - { - Events.RefreshingServiceIdentities(refreshRequest.DeviceIds); - await this.deviceScopeIdentitiesCache.RefreshServiceIdentities(refreshRequest.DeviceIds); - Events.RefreshedServiceIdentities(refreshRequest.DeviceIds); - return new DirectMethodResponse(request.CorrelationId, null, (int)HttpStatusCode.OK); - } - catch (Exception e) - { - Events.ErrorRefreshingServiceIdentities(e); - return new DirectMethodResponse(e, HttpStatusCode.InternalServerError); - } - } - else - { - Events.InvalidMethodRequest(request.Name); - return new DirectMethodResponse(new InvalidOperationException($"Method {request.Name} is not supported"), HttpStatusCode.NotFound); - } - } - - class RefreshRequest - { - [JsonConstructor] - public RefreshRequest(IEnumerable deviceIds) - { - this.DeviceIds = deviceIds; - } - - [JsonProperty("deviceIds")] - public IEnumerable DeviceIds { get; } - } - - class DesiredProperties + internal class LastDesiredStatus { - [JsonConstructor] - public DesiredProperties(string schemaVersion, IDictionary routes, StoreAndForwardConfiguration storeAndForwardConfiguration) + public LastDesiredStatus(int code, string description) { - this.SchemaVersion = schemaVersion; - this.Routes = routes; - this.StoreAndForwardConfiguration = storeAndForwardConfiguration; + this.Code = code; + this.Description = description; } - public string SchemaVersion { get; } - - public IDictionary Routes { get; } + [JsonProperty(PropertyName = "code")] + public int Code { get; set; } - public StoreAndForwardConfiguration StoreAndForwardConfiguration { get; } + [JsonProperty(PropertyName = "description")] + public string Description { get; set; } } internal class ReportedProperties { const string CurrentSchemaVersion = "1.0"; - static Dictionary EmptyConnectionStatusesDictionary = new Dictionary(); + static readonly Dictionary EmptyConnectionStatusesDictionary = new Dictionary(); // When reporting last desired version/status, send empty map of clients so that the patch doesn't touch the // existing values. If we send a null, it will clear out the existing clients. @@ -422,14 +408,17 @@ public ReportedProperties(VersionInfo versionInfo, long lastDesiredVersion, Last } public ReportedProperties(VersionInfo versionInfo, IDictionary clients) - : this(versionInfo, CurrentSchemaVersion, null, null, clients) { } + : this(versionInfo, CurrentSchemaVersion, null, null, clients) + { + } [JsonConstructor] public ReportedProperties( - VersionInfo versionInfo, string schemaVersion, - long? lastDesiredVersion, LastDesiredStatus lastDesiredStatus, - IDictionary clients - ) + VersionInfo versionInfo, + string schemaVersion, + long? lastDesiredVersion, + LastDesiredStatus lastDesiredStatus, + IDictionary clients) { this.SchemaVersion = schemaVersion; this.LastDesiredVersion = lastDesiredVersion; @@ -454,19 +443,21 @@ IDictionary clients public VersionInfo VersionInfo { get; } } - internal class LastDesiredStatus + class DesiredProperties { - public LastDesiredStatus(int code, string description) + [JsonConstructor] + public DesiredProperties(string schemaVersion, IDictionary routes, StoreAndForwardConfiguration storeAndForwardConfiguration) { - this.Code = code; - this.Description = description; + this.SchemaVersion = schemaVersion; + this.Routes = routes; + this.StoreAndForwardConfiguration = storeAndForwardConfiguration; } - [JsonProperty(PropertyName = "code")] - public int Code { get; set; } + public string SchemaVersion { get; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + public IDictionary Routes { get; } + + public StoreAndForwardConfiguration StoreAndForwardConfiguration { get; } } /// @@ -518,8 +509,8 @@ public Task SendTwinUpdate(IMessage twin) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.EdgeHubConnection; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -543,114 +534,143 @@ enum EventIds MethodRequestReceived } - internal static void Initialized(IIdentity edgeHubIdentity) + public static void ErrorGettingEdgeHubConfig(Exception ex) { - Log.LogDebug((int)EventIds.Initialized, Invariant($"Initialized connection for {edgeHubIdentity.Id}")); + Log.LogWarning( + (int)EventIds.ErrorPatchingDesiredProperties, + ex, + Invariant($"Error getting edge hub config from twin desired properties")); } - internal static void ErrorUpdatingLastDesiredStatus(Exception ex) + public static void MismatchedMinorVersions(Version receivedVersion, Version expectedVersion) { - Log.LogWarning((int)EventIds.ErrorUpdatingLastDesiredStatus, ex, - Invariant($"Error updating last desired status for edge hub")); + Log.LogWarning( + (int)EventIds.MismatchedSchemaVersion, + Invariant($"Desired properties schema version {receivedVersion} does not match expected schema version {expectedVersion}. Some settings may not be supported.")); } - internal static void ErrorHandlingDesiredPropertiesUpdate(Exception ex) + public static void ErrorParsingMethodRequest(Exception ex) { - Log.LogWarning((int)EventIds.ErrorHandlingDesiredPropertiesUpdate, ex, - Invariant($"Error handling desired properties update for edge hub")); + Log.LogWarning((int)EventIds.ErrorParsingMethodRequest, ex, Invariant($"Error parsing refresh service identities request")); } - internal static void ErrorPatchingDesiredProperties(Exception ex) + public static void ErrorRefreshingServiceIdentities(Exception ex) { - Log.LogWarning((int)EventIds.ErrorPatchingDesiredProperties, ex, - Invariant($"Error merging desired properties patch with existing desired properties for edge hub")); + Log.LogWarning((int)EventIds.ErrorRefreshingServiceIdentities, ex, Invariant($"Error refreshing service identities")); } - internal static void ErrorHandlingDeviceConnectedEvent(IIdentity device, Exception ex) + public static void RefreshedServiceIdentities(IEnumerable refreshRequestDeviceIds) { - Log.LogWarning((int)EventIds.ErrorHandlingDeviceConnectedEvent, ex, - Invariant($"Error handling device connected event for device {device?.Id ?? string.Empty}")); + Log.LogInformation((int)EventIds.RefreshedServiceIdentities, Invariant($"Refreshed {refreshRequestDeviceIds.Count()} device scope identities on demand")); } - internal static void ErrorHandlingDeviceDisconnectedEvent(IIdentity device, Exception ex) + public static void RefreshingServiceIdentities(IEnumerable refreshRequestDeviceIds) { - Log.LogWarning((int)EventIds.ErrorHandlingDeviceDisconnectedEvent, ex, - Invariant($"Error handling device disconnected event for device {device?.Id ?? string.Empty}")); + Log.LogDebug((int)EventIds.RefreshedServiceIdentities, Invariant($"Refreshing {refreshRequestDeviceIds.Count()} device scope identities")); } - internal static void ErrorUpdatingDeviceConnectionStatus(string deviceId, Exception ex) + public static void MethodRequestReceived(string methodName) { - Log.LogWarning((int)EventIds.ErrorUpdatingDeviceConnectionStatus, ex, - Invariant($"Error updating device connection status for device {deviceId ?? string.Empty}")); + Log.LogDebug((int)EventIds.MethodRequestReceived, Invariant($"Received method request {methodName}")); } - public static void ErrorGettingEdgeHubConfig(Exception ex) + public static void InvalidMethodRequest(string requestName) { - Log.LogWarning((int)EventIds.ErrorPatchingDesiredProperties, ex, - Invariant($"Error getting edge hub config from twin desired properties")); + Log.LogWarning((int)EventIds.InvalidMethodRequest, Invariant($"Received request for unsupported method {requestName}")); } - internal static void GetConfigSuccess() + public static void SkipUpdatingEdgeHubIdentity(string id, ConnectionStatus connectionStatus) { - Log.LogInformation((int)EventIds.GetConfigSuccess, Invariant($"Obtained edge hub config from module twin")); + Log.LogDebug((int)EventIds.SkipUpdatingEdgeHubIdentity, Invariant($"Skipped updating connection status change to {connectionStatus} for {id}")); } - internal static void PatchConfigSuccess() + internal static void Initialized(IIdentity edgeHubIdentity) { - Log.LogInformation((int)EventIds.PatchConfigSuccess, Invariant($"Obtained edge hub config patch update from module twin")); + Log.LogDebug((int)EventIds.Initialized, Invariant($"Initialized connection for {edgeHubIdentity.Id}")); } - internal static void ErrorClearingDeviceConnectionStatuses(Exception ex) + internal static void ErrorUpdatingLastDesiredStatus(Exception ex) { - Log.LogWarning((int)EventIds.ErrorClearingDeviceConnectionStatuses, ex, - Invariant($"Error clearing device connection statuses")); + Log.LogWarning( + (int)EventIds.ErrorUpdatingLastDesiredStatus, + ex, + Invariant($"Error updating last desired status for edge hub")); } - internal static void UpdatingDeviceConnectionStatus(string deviceId, ConnectionStatus connectionStatus) + internal static void ErrorHandlingDesiredPropertiesUpdate(Exception ex) { - Log.LogDebug((int)EventIds.UpdatingDeviceConnectionStatus, Invariant($"Updating device {deviceId} connection status to {connectionStatus}")); + Log.LogWarning( + (int)EventIds.ErrorHandlingDesiredPropertiesUpdate, + ex, + Invariant($"Error handling desired properties update for edge hub")); } - public static void MismatchedMinorVersions(Version receivedVersion, Version expectedVersion) + internal static void ErrorPatchingDesiredProperties(Exception ex) { - Log.LogWarning((int)EventIds.MismatchedSchemaVersion, - Invariant($"Desired properties schema version {receivedVersion} does not match expected schema version {expectedVersion}. Some settings may not be supported.")); + Log.LogWarning( + (int)EventIds.ErrorPatchingDesiredProperties, + ex, + Invariant($"Error merging desired properties patch with existing desired properties for edge hub")); } - public static void ErrorParsingMethodRequest(Exception ex) + internal static void ErrorHandlingDeviceConnectedEvent(IIdentity device, Exception ex) { - Log.LogWarning((int)EventIds.ErrorParsingMethodRequest, ex, Invariant($"Error parsing refresh service identities request")); + Log.LogWarning( + (int)EventIds.ErrorHandlingDeviceConnectedEvent, + ex, + Invariant($"Error handling device connected event for device {device?.Id ?? string.Empty}")); } - public static void ErrorRefreshingServiceIdentities(Exception ex) + internal static void ErrorHandlingDeviceDisconnectedEvent(IIdentity device, Exception ex) { - Log.LogWarning((int)EventIds.ErrorRefreshingServiceIdentities, ex, Invariant($"Error refreshing service identities")); + Log.LogWarning( + (int)EventIds.ErrorHandlingDeviceDisconnectedEvent, + ex, + Invariant($"Error handling device disconnected event for device {device?.Id ?? string.Empty}")); } - public static void RefreshedServiceIdentities(IEnumerable refreshRequestDeviceIds) + internal static void ErrorUpdatingDeviceConnectionStatus(string deviceId, Exception ex) { - Log.LogInformation((int)EventIds.RefreshedServiceIdentities, Invariant($"Refreshed {refreshRequestDeviceIds.Count()} device scope identities on demand")); + Log.LogWarning( + (int)EventIds.ErrorUpdatingDeviceConnectionStatus, + ex, + Invariant($"Error updating device connection status for device {deviceId ?? string.Empty}")); } - public static void RefreshingServiceIdentities(IEnumerable refreshRequestDeviceIds) + internal static void GetConfigSuccess() { - Log.LogDebug((int)EventIds.RefreshedServiceIdentities, Invariant($"Refreshing {refreshRequestDeviceIds.Count()} device scope identities")); + Log.LogInformation((int)EventIds.GetConfigSuccess, Invariant($"Obtained edge hub config from module twin")); } - public static void MethodRequestReceived(string methodName) + internal static void PatchConfigSuccess() { - Log.LogDebug((int)EventIds.MethodRequestReceived, Invariant($"Received method request {methodName}")); + Log.LogInformation((int)EventIds.PatchConfigSuccess, Invariant($"Obtained edge hub config patch update from module twin")); } - public static void InvalidMethodRequest(string requestName) + internal static void ErrorClearingDeviceConnectionStatuses(Exception ex) { - Log.LogWarning((int)EventIds.InvalidMethodRequest, Invariant($"Received request for unsupported method {requestName}")); + Log.LogWarning( + (int)EventIds.ErrorClearingDeviceConnectionStatuses, + ex, + Invariant($"Error clearing device connection statuses")); } - public static void SkipUpdatingEdgeHubIdentity(string id, ConnectionStatus connectionStatus) + internal static void UpdatingDeviceConnectionStatus(string deviceId, ConnectionStatus connectionStatus) { - Log.LogDebug((int)EventIds.SkipUpdatingEdgeHubIdentity, Invariant($"Skipped updating connection status change to {connectionStatus} for {id}")); + Log.LogDebug((int)EventIds.UpdatingDeviceConnectionStatus, Invariant($"Updating device {deviceId} connection status to {connectionStatus}")); } } + + class RefreshRequest + { + [JsonConstructor] + public RefreshRequest(IEnumerable deviceIds) + { + this.DeviceIds = deviceIds; + } + + [JsonProperty("deviceIds")] + public IEnumerable DeviceIds { get; } + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnectionException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnectionException.cs index c2361e49e28..63e59dc98be 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnectionException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubConnectionException.cs @@ -8,10 +8,12 @@ public class EdgeHubConnectionException : IOException { public EdgeHubConnectionException(string message) : this(message, null) - { } + { + } - public EdgeHubConnectionException(string message, Exception innerException) - : base(message, innerException) - { } + public EdgeHubConnectionException(string message, Exception innerException) + : base(message, innerException) + { + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubIOException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubIOException.cs index d385d6210ec..7bfb9ed1cda 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubIOException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubIOException.cs @@ -8,10 +8,12 @@ public class EdgeHubIOException : IOException { public EdgeHubIOException(string message) : this(message, null) - { } + { + } public EdgeHubIOException(string message, Exception innerException) - : base(message, innerException) - { } + : base(message, innerException) + { + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubMessageTooLargeException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubMessageTooLargeException.cs index c286b283b56..682413d9174 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubMessageTooLargeException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubMessageTooLargeException.cs @@ -7,10 +7,12 @@ public class EdgeHubMessageTooLargeException : Exception { public EdgeHubMessageTooLargeException(string message) : base(message) - { } + { + } public EdgeHubMessageTooLargeException(string message, Exception innerException) : base(message, innerException) - { } + { + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubTimeoutException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubTimeoutException.cs index 38cb5e52b13..aa7ea97e736 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubTimeoutException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeHubTimeoutException.cs @@ -7,10 +7,12 @@ public class EdgeHubTimeoutException : Exception { public EdgeHubTimeoutException(string message) : base(message) - { } + { + } public EdgeHubTimeoutException(string message, Exception innerException) : base(message, innerException) - { } + { + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeMessage.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeMessage.cs index 4e6f7d429a1..3929676f8a3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeMessage.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/EdgeMessage.cs @@ -8,12 +8,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public class EdgeMessage : IMessage { - public byte[] Body { get; } - - public IDictionary Properties { get; } - - public IDictionary SystemProperties { get; } - public EdgeMessage(byte[] body, IDictionary properties, IDictionary systemProperties) { this.Body = Preconditions.CheckNotNull(body, nameof(body)); @@ -21,6 +15,12 @@ public EdgeMessage(byte[] body, IDictionary properties, IDiction this.SystemProperties = Preconditions.CheckNotNull(systemProperties, nameof(systemProperties)); } + public byte[] Body { get; } + + public IDictionary Properties { get; } + + public IDictionary SystemProperties { get; } + public bool Equals(EdgeMessage other) { if (ReferenceEquals(null, other)) @@ -34,10 +34,10 @@ public bool Equals(EdgeMessage other) } return this.Body.SequenceEqual(other.Body) && - this.Properties.Keys.Count == other.Properties.Keys.Count && - this.Properties.Keys.All(key => other.Properties.ContainsKey(key) && Equals(this.Properties[key], other.Properties[key])) && - this.SystemProperties.Keys.Count == other.SystemProperties.Keys.Count && - this.SystemProperties.Keys.All(skey => other.SystemProperties.ContainsKey(skey) && Equals(this.SystemProperties[skey], other.SystemProperties[skey])); + this.Properties.Keys.Count == other.Properties.Keys.Count && + this.Properties.Keys.All(key => other.Properties.ContainsKey(key) && Equals(this.Properties[key], other.Properties[key])) && + this.SystemProperties.Keys.Count == other.SystemProperties.Keys.Count && + this.SystemProperties.Keys.All(skey => other.SystemProperties.ContainsKey(skey) && Equals(this.SystemProperties[skey], other.SystemProperties[skey])); } public override bool Equals(object obj) @@ -104,7 +104,6 @@ public EdgeMessage Build() return new EdgeMessage(this.body, this.properties, this.systemProperties); } - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/HubCoreEventIds.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/HubCoreEventIds.cs index dd85e09affc..2a153448261 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/HubCoreEventIds.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/HubCoreEventIds.cs @@ -1,24 +1,23 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Core { - public static class HubCoreEventIds - { - const int EventIdStart = 1000; - public const int ConnectionManager = EventIdStart; - public const int DeviceListener = EventIdStart + 100; - public const int CloudEndpoint = EventIdStart + 200; - public const int ModuleEndpoint = EventIdStart + 300; - public const int Authenticator = EventIdStart + 400; - public const int RoutingEdgeHub = EventIdStart + 500; - public const int MessageStore = EventIdStart + 600; - public const int TwinManager = EventIdStart + 700; - public const int ConfigUpdater = EventIdStart + 800; - public const int EdgeHubConnection = EventIdStart + 900; - public const int TokenCredentialsStore = EventIdStart + 1000; - public const int InvokeMethodHandler = EventIdStart + 1100; - public const int DeviceScopeIdentitiesCache = EventIdStart + 1200; - public const int PeriodicConnectionAuthenticator = EventIdStart + 1300; - public const int DeviceScopeAuthenticator = EventIdStart + 1400; - - } + public static class HubCoreEventIds + { + public const int ConnectionManager = EventIdStart; + public const int DeviceListener = EventIdStart + 100; + public const int CloudEndpoint = EventIdStart + 200; + public const int ModuleEndpoint = EventIdStart + 300; + public const int Authenticator = EventIdStart + 400; + public const int RoutingEdgeHub = EventIdStart + 500; + public const int MessageStore = EventIdStart + 600; + public const int TwinManager = EventIdStart + 700; + public const int ConfigUpdater = EventIdStart + 800; + public const int EdgeHubConnection = EventIdStart + 900; + public const int TokenCredentialsStore = EventIdStart + 1000; + public const int InvokeMethodHandler = EventIdStart + 1100; + public const int DeviceScopeIdentitiesCache = EventIdStart + 1200; + public const int PeriodicConnectionAuthenticator = EventIdStart + 1300; + public const int DeviceScopeAuthenticator = EventIdStart + 1400; + const int EventIdStart = 1000; + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IConnectionManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IConnectionManager.cs index df2c859cbae..7075890ee6d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IConnectionManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IConnectionManager.cs @@ -21,6 +21,14 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core /// public interface IConnectionManager { + event EventHandler CloudConnectionEstablished; + + event EventHandler CloudConnectionLost; + + event EventHandler DeviceConnected; + + event EventHandler DeviceDisconnected; + Task AddDeviceConnection(IIdentity identity, IDeviceProxy deviceProxy); Task RemoveDeviceConnection(string id); @@ -38,13 +46,5 @@ public interface IConnectionManager Option> GetSubscriptions(string id); IEnumerable GetConnectedClients(); - - event EventHandler CloudConnectionLost; - - event EventHandler CloudConnectionEstablished; - - event EventHandler DeviceConnected; - - event EventHandler DeviceDisconnected; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceConnectivityManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceConnectivityManager.cs index 6f08001fb03..cfb33a26868 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceConnectivityManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceConnectivityManager.cs @@ -5,10 +5,12 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public interface IDeviceConnectivityManager { - void CallSucceeded(); - void CallTimedOut(); - event EventHandler DeviceConnected; + event EventHandler DeviceDisconnected; + + void CallSucceeded(); + + void CallTimedOut(); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceScopeIdentitiesCache.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceScopeIdentitiesCache.cs index 7388bcca3c5..2a7f4696679 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceScopeIdentitiesCache.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IDeviceScopeIdentitiesCache.cs @@ -9,6 +9,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public interface IDeviceScopeIdentitiesCache { + event EventHandler ServiceIdentityRemoved; + + event EventHandler ServiceIdentityUpdated; + Task> GetServiceIdentity(string id, bool refreshIfNotExists = false); void InitiateCacheRefresh(); @@ -16,9 +20,5 @@ public interface IDeviceScopeIdentitiesCache Task RefreshServiceIdentities(IEnumerable ids); Task RefreshServiceIdentity(string id); - - event EventHandler ServiceIdentityUpdated; - - event EventHandler ServiceIdentityRemoved; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IEdgeHub.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IEdgeHub.cs index 21ca9a0d77e..be3beea96ce 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IEdgeHub.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IEdgeHub.cs @@ -1,36 +1,36 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Core { - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Hub.Core.Device; - using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Hub.Core.Device; + using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; /// - /// The IEdgeHub is responsible for processing messages sent to the - /// edge hub by devices and modules. The - /// for instance handles this by having the router process the message by - /// executing the routing rules it is configured with. - /// - public interface IEdgeHub : IDisposable - { - Task ProcessDeviceMessage(IIdentity identity, IMessage message); + /// The IEdgeHub is responsible for processing messages sent to the + /// edge hub by devices and modules. The + /// for instance handles this by having the router process the message by + /// executing the routing rules it is configured with. + /// + public interface IEdgeHub : IDisposable + { + Task ProcessDeviceMessage(IIdentity identity, IMessage message); - Task ProcessDeviceMessageBatch(IIdentity identity, IEnumerable message); + Task ProcessDeviceMessageBatch(IIdentity identity, IEnumerable message); - Task InvokeMethodAsync(string id, DirectMethodRequest methodRequest); + Task InvokeMethodAsync(string id, DirectMethodRequest methodRequest); - Task UpdateReportedPropertiesAsync(IIdentity identity, IMessage reportedPropertiesMessage); + Task UpdateReportedPropertiesAsync(IIdentity identity, IMessage reportedPropertiesMessage); - Task GetTwinAsync(string id); + Task GetTwinAsync(string id); - Task UpdateDesiredPropertiesAsync(string id, IMessage twinCollection); + Task UpdateDesiredPropertiesAsync(string id, IMessage twinCollection); - Task SendC2DMessageAsync(string id, IMessage message); + Task SendC2DMessageAsync(string id, IMessage message); - Task AddSubscription(string id, DeviceSubscription deviceSubscription); + Task AddSubscription(string id, DeviceSubscription deviceSubscription); - Task RemoveSubscription(string id, DeviceSubscription deviceSubscription); - } + Task RemoveSubscription(string id, DeviceSubscription deviceSubscription); + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IInvokeMethodHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IInvokeMethodHandler.cs index 81237299229..59ca17e6fa5 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IInvokeMethodHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IInvokeMethodHandler.cs @@ -8,7 +8,7 @@ public interface IInvokeMethodHandler Task InvokeMethod(DirectMethodRequest request); /// - /// This method is called when a client subscribes to Method invocations. + /// This method is called when a client subscribes to Method invocations. /// It processes all the pending method requests for that client (i.e the method requests /// that came in before the client subscribed to method invocations and that haven't expired yet) /// diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IServiceIdentitiesIterator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IServiceIdentitiesIterator.cs index 2700b34fbde..083600ba4bd 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IServiceIdentitiesIterator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IServiceIdentitiesIterator.cs @@ -7,8 +7,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public interface IServiceIdentitiesIterator { - Task> GetNext(); - bool HasNext { get; } + + Task> GetNext(); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ITwinManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ITwinManager.cs index 6916017202c..148e4c212f3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ITwinManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/ITwinManager.cs @@ -4,11 +4,11 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core using System.Threading.Tasks; public interface ITwinManager - { - Task GetTwinAsync(string id); + { + Task GetTwinAsync(string id); - Task UpdateDesiredPropertiesAsync(string id, IMessage twinCollection); + Task UpdateDesiredPropertiesAsync(string id, IMessage twinCollection); - Task UpdateReportedPropertiesAsync(string id, IMessage twinCollection); - } + Task UpdateReportedPropertiesAsync(string id, IMessage twinCollection); + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IWebSocketListener.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IWebSocketListener.cs index 408598ae62b..8e0616efd55 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IWebSocketListener.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/IWebSocketListener.cs @@ -12,16 +12,18 @@ public interface IWebSocketListener { string SubProtocol { get; } - Task ProcessWebSocketRequestAsync(WebSocket webSocket, - Option localEndPoint, - EndPoint remoteEndPoint, - string correlationId); + Task ProcessWebSocketRequestAsync( + WebSocket webSocket, + Option localEndPoint, + EndPoint remoteEndPoint, + string correlationId); - Task ProcessWebSocketRequestAsync(WebSocket webSocket, - Option localEndPoint, - EndPoint remoteEndPoint, - string correlationId, - X509Certificate2 clientCert, - IList clientCertChain); + Task ProcessWebSocketRequestAsync( + WebSocket webSocket, + Option localEndPoint, + EndPoint remoteEndPoint, + string correlationId, + X509Certificate2 clientCert, + IList clientCertChain); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/InvokeMethodHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/InvokeMethodHandler.cs index 260d84f9e2e..9999d24e293 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/InvokeMethodHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/InvokeMethodHandler.cs @@ -54,7 +54,7 @@ public Task InvokeMethod(DirectMethodRequest methodRequest } /// - /// This method is called when a client subscribes to Method invocations. + /// This method is called when a client subscribes to Method invocations. /// It processes all the pending method requests for that client (i.e the method requests /// that came in before the client subscribed to method invocations and that haven't expired yet) /// @@ -67,7 +67,7 @@ public Task ProcessInvokeMethodSubscription(string id) } /// - /// This method is used to process all pending method requests, but without waiting for the + /// This method is used to process all pending method requests, but without waiting for the /// processing to complete /// async void ProcessInvokeMethodSubscriptionInternal(string id) @@ -78,7 +78,7 @@ async void ProcessInvokeMethodSubscriptionInternal(string id) // Temporary hack to wait for the subscription call to complete. Without this, // the EdgeHub will invoke the pending method request "too soon", before the layers // in between have been set up correctly. To fix this, changes are needed in ProtocolGateway, - // Client SDK, and need to figure out a way to raise events for AMQP Links. + // Client SDK, and need to figure out a way to raise events for AMQP Links. await Task.Delay(TimeSpan.FromSeconds(3)); await this.ProcessInvokeMethodsForClient(id); } @@ -105,6 +105,7 @@ Option GetDeviceProxyWithSubscription(string id) { return deviceProxy; } + return Option.None(); } @@ -132,8 +133,8 @@ ConcurrentDictionary(); const int IdStart = HubCoreEventIds.InvokeMethodHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MessageConverterProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MessageConverterProvider.cs index 050f55475f5..6a366757c1d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MessageConverterProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MessageConverterProvider.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using Microsoft.Azure.Devices.Edge.Util; - namespace Microsoft.Azure.Devices.Edge.Hub.Core { + using System; + using System.Collections.Generic; + using Microsoft.Azure.Devices.Edge.Util; + public class MessageConverterProvider : IMessageConverterProvider { readonly IDictionary converters; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Microsoft.Azure.Devices.Edge.Hub.Core.csproj b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Microsoft.Azure.Devices.Edge.Hub.Core.csproj index 192e480bc7e..b00b3607757 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Microsoft.Azure.Devices.Edge.Hub.Core.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/Microsoft.Azure.Devices.Edge.Hub.Core.csproj @@ -40,4 +40,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MultipleConnectionsException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MultipleConnectionsException.cs index 8d2cdad8072..118510f4de0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MultipleConnectionsException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/MultipleConnectionsException.cs @@ -6,7 +6,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public class MultipleConnectionsException : Exception { public MultipleConnectionsException(string message) - : base (message) - { } + : base(message) + { + } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/NullDeviceScopeIdentitiesCache.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/NullDeviceScopeIdentitiesCache.cs index 2741b9fbc74..45ba6357a66 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/NullDeviceScopeIdentitiesCache.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/NullDeviceScopeIdentitiesCache.cs @@ -9,6 +9,18 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public class NullDeviceScopeIdentitiesCache : IDeviceScopeIdentitiesCache { + public event EventHandler ServiceIdentityRemoved + { + add { } + remove { } + } + + public event EventHandler ServiceIdentityUpdated + { + add { } + remove { } + } + public Task> GetServiceIdentity(string id, bool refreshIfNotExists = false) => Task.FromResult(Option.None()); @@ -24,9 +36,5 @@ public void InitiateCacheRefresh() public Task RefreshServiceIdentity(string deviceId) => Task.CompletedTask; public Task RefreshServiceIdentity(string deviceId, string moduleId) => Task.CompletedTask; - - public event EventHandler ServiceIdentityUpdated { add { } remove { } } - - public event EventHandler ServiceIdentityRemoved { add { } remove { } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/PersistedTokenCredentialsCache.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/PersistedTokenCredentialsCache.cs index 69471ef23ad..f99eaf39e6a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/PersistedTokenCredentialsCache.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/PersistedTokenCredentialsCache.cs @@ -38,20 +38,22 @@ public async Task Add(IClientCredentials clientCredentials) public async Task> Get(IIdentity identity) { Option tokenCredentialsDataOption = await this.encryptedStore.Get(identity.Id); - return tokenCredentialsDataOption.FlatMap(t => - { - Events.Retrieved(identity.Id); - try - { - return Option.Some(ParseTokenCredentialsData(identity, t) - .GetOrElse(() => new TokenCredentials(identity, t, string.Empty, false) as IClientCredentials)); - } - catch (Exception e) + return tokenCredentialsDataOption.FlatMap( + t => { - Events.ErrorGetting(e, identity.Id); - return Option.None(); - } - }); + Events.Retrieved(identity.Id); + try + { + return Option.Some( + ParseTokenCredentialsData(identity, t) + .GetOrElse(() => new TokenCredentials(identity, t, string.Empty, false) as IClientCredentials)); + } + catch (Exception e) + { + Events.ErrorGetting(e, identity.Id); + return Option.None(); + } + }); } static Option ParseTokenCredentialsData(IIdentity identity, string json) @@ -69,26 +71,10 @@ static Option ParseTokenCredentialsData(IIdentity identity, } } - class TokenCredentialsData - { - [JsonConstructor] - public TokenCredentialsData(string token, bool isUpdatable) - { - this.Token = token; - this.IsUpdatable = isUpdatable; - } - - [JsonProperty("isUpdatable")] - public bool IsUpdatable { get; } - - [JsonProperty("token")] - public string Token { get; } - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.TokenCredentialsStore; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -124,5 +110,21 @@ public static void ErrorParsingData(string id, Exception ex) Log.LogDebug((int)EventIds.ErrorParsingData, ex, $"Error parsing persisted token credentials data for {id}, treating it as the token string instead."); } } + + class TokenCredentialsData + { + [JsonConstructor] + public TokenCredentialsData(string token, bool isUpdatable) + { + this.Token = token; + this.IsUpdatable = isUpdatable; + } + + [JsonProperty("isUpdatable")] + public bool IsUpdatable { get; } + + [JsonProperty("token")] + public string Token { get; } + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/SystemProperties.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/SystemProperties.cs index 7f163e6116d..171d7549941 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/SystemProperties.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/SystemProperties.cs @@ -34,24 +34,6 @@ public static class SystemProperties public const string Operation = "operation"; public const string SequenceNumber = "sequenceNumber"; - static class OnTheWireSystemPropertyNames - { - public const string ExpiryTimeUtcOnTheWireName = "$.exp"; - public const string CorrelationIdOnTheWireName = "$.cid"; - public const string MessageIdOnTheWireName = "$.mid"; - public const string ToOnTheWireName = "$.to"; - public const string UserIdOnTheWireName = "$.uid"; - public const string AckOnTheWireName = "ack"; - public const string OutputNameOnTheWireName = "$.on"; - public const string ConnectionDeviceIdOnTheWireName = "$.cdid"; - public const string ConnectionModuleIdOnTheWireName = "$.cmid"; - public const string ContentTypeOnTheWireName = "$.ct"; - public const string ContentEncodingOnTheWireName = "$.ce"; - public const string MessageSchemaOnTheWireName = "$.schema"; - public const string CreationTimeOnTheWireName = "$.ctime"; - public const string OperationOnTheWireName = "iothub-operation"; - } - public static readonly Dictionary IncomingSystemPropertiesMap = new Dictionary { { OnTheWireSystemPropertyNames.ExpiryTimeUtcOnTheWireName, ExpiryTimeUtc }, @@ -84,5 +66,23 @@ static class OnTheWireSystemPropertyNames { ConnectionDeviceId, OnTheWireSystemPropertyNames.ConnectionDeviceIdOnTheWireName }, { ConnectionModuleId, OnTheWireSystemPropertyNames.ConnectionModuleIdOnTheWireName } }; + + static class OnTheWireSystemPropertyNames + { + public const string ExpiryTimeUtcOnTheWireName = "$.exp"; + public const string CorrelationIdOnTheWireName = "$.cid"; + public const string MessageIdOnTheWireName = "$.mid"; + public const string ToOnTheWireName = "$.to"; + public const string UserIdOnTheWireName = "$.uid"; + public const string AckOnTheWireName = "ack"; + public const string OutputNameOnTheWireName = "$.on"; + public const string ConnectionDeviceIdOnTheWireName = "$.cdid"; + public const string ConnectionModuleIdOnTheWireName = "$.cmid"; + public const string ContentTypeOnTheWireName = "$.ct"; + public const string ContentEncodingOnTheWireName = "$.ce"; + public const string MessageSchemaOnTheWireName = "$.schema"; + public const string CreationTimeOnTheWireName = "$.ctime"; + public const string OperationOnTheWireName = "iothub-operation"; + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinInfo.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinInfo.cs index 4e1b9a3425e..e51270dc8b8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinInfo.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinInfo.cs @@ -6,15 +6,15 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core public class TwinInfo { - public Twin Twin { get; } - - public TwinCollection ReportedPropertiesPatch { get; } - [JsonConstructor] public TwinInfo(Twin twin, TwinCollection reportedPropertiesPatch) { this.Twin = twin; this.ReportedPropertiesPatch = reportedPropertiesPatch; } + + public Twin Twin { get; } + + public TwinCollection ReportedPropertiesPatch { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinManager.cs index 5ef6fa167bb..a455328fdb1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinManager.cs @@ -1,6 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Core { + using System; + using System.Collections.Generic; + using System.Text; + using System.Threading.Tasks; + using System.Threading.Tasks.Dataflow; using JetBrains.Annotations; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; @@ -11,11 +16,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; - using System; - using System.Collections.Generic; - using System.Text; - using System.Threading.Tasks; - using System.Threading.Tasks.Dataflow; public class TwinManager : ITwinManager { @@ -30,7 +30,6 @@ public class TwinManager : ITwinManager readonly AsyncLock reportedPropertiesLock; readonly AsyncLock twinLock; readonly ActionBlock actionBlock; - internal Option> TwinStore { get; } public TwinManager(IConnectionManager connectionManager, IMessageConverter twinCollectionConverter, IMessageConverter twinConverter, Option> twinStore) { @@ -43,6 +42,8 @@ public TwinManager(IConnectionManager connectionManager, IMessageConverter(this.ProcessConnectionEstablishedForDevice); } + internal Option> TwinStore { get; } + public static ITwinManager CreateTwinManager( IConnectionManager connectionManager, IMessageConverterProvider messageConverterProvider, @@ -51,7 +52,10 @@ public static ITwinManager CreateTwinManager( Preconditions.CheckNotNull(connectionManager, nameof(connectionManager)); Preconditions.CheckNotNull(messageConverterProvider, nameof(messageConverterProvider)); Preconditions.CheckNotNull(storeProvider, nameof(storeProvider)); - var twinManager = new TwinManager(connectionManager, messageConverterProvider.Get(), messageConverterProvider.Get(), + var twinManager = new TwinManager( + connectionManager, + messageConverterProvider.Get(), + messageConverterProvider.Get(), storeProvider.Match( s => Option.Some(s.GetEntityStore(Constants.TwinStorePartitionKey)), () => Option.None>())); @@ -59,6 +63,234 @@ public static ITwinManager CreateTwinManager( return twinManager; } + public async Task GetTwinAsync(string id) + { + return await this.TwinStore.Match( + async (store) => + { + TwinInfo twinInfo = await this.GetTwinInfoWithStoreSupportAsync(id); + return twinInfo.Twin != null + ? this.twinConverter.ToMessage(twinInfo.Twin) + : throw new InvalidOperationException($"Error getting twin for device {id}. Twin is null."); + }, + async () => + { + // pass through to cloud proxy + Option cloudProxy = await this.connectionManager.GetCloudConnection(id); + return await cloudProxy.Match(async (cp) => await cp.GetTwinAsync(), () => throw new InvalidOperationException($"Cloud proxy unavailable for device {id}")); + }); + } + + public async Task UpdateDesiredPropertiesAsync(string id, IMessage desiredProperties) + { + await this.TwinStore.Map( + s => this.UpdateDesiredPropertiesWithStoreSupportAsync(id, desiredProperties)).GetOrElse( + () => this.SendDesiredPropertiesToDeviceProxy(id, desiredProperties)); + } + + public async Task UpdateReportedPropertiesAsync(string id, IMessage reportedProperties) + { + if (!this.TwinStore.HasValue) + { + await this.SendReportedPropertiesToCloudProxy(id, reportedProperties); + } + else + { + await this.UpdateReportedPropertiesWithStoreSupportAsync(id, reportedProperties); + } + } + + internal static void ValidateTwinProperties(JToken properties) => ValidateTwinProperties(properties, 1); + + internal void ConnectionEstablishedCallback(object sender, IIdentity identity) + { + Events.ConnectionEstablished(identity.Id); + this.actionBlock.Post(identity); + } + + internal async Task ExecuteOnTwinStoreResultAsync(string id, Func twinStoreHit, Func twinStoreMiss) + { + Option cached = await this.TwinStore.Match(s => s.Get(id), () => throw new InvalidOperationException("Missing twin store")); + + if (!cached.HasValue) + { + await twinStoreMiss(); + } + else + { + await cached.ForEachAsync(c => twinStoreHit(c)); + } + } + + internal async Task GetTwinInfoWhenCloudOnlineAsync(string id, ICloudProxy cp, bool sendDesiredPropertyUpdate) + { + TwinCollection diff = null; + // Used for returning value to caller + TwinInfo cached; + + using (await this.twinLock.LockAsync()) + { + IMessage twinMessage = await cp.GetTwinAsync(); + Twin cloudTwin = this.twinConverter.FromMessage(twinMessage); + Events.GotTwinFromCloudSuccess(id, cloudTwin.Properties.Desired.Version, cloudTwin.Properties.Reported.Version); + var newTwin = new TwinInfo(cloudTwin, null); + cached = newTwin; + + IEntityStore twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); + + await twinStore.PutOrUpdate( + id, + newTwin, + t => + { + // If the new twin is more recent than the cached twin, update the cached copy. + // If not, reject the cloud twin + if (t.Twin == null || + cloudTwin.Properties.Desired.Version > t.Twin.Properties.Desired.Version || + cloudTwin.Properties.Reported.Version > t.Twin.Properties.Reported.Version) + { + if (t.Twin != null) + { + Events.UpdateCachedTwin( + id, + t.Twin.Properties.Desired.Version, + cloudTwin.Properties.Desired.Version, + t.Twin.Properties.Reported.Version, + cloudTwin.Properties.Reported.Version); + cached = new TwinInfo(cloudTwin, t.ReportedPropertiesPatch); + // If the device is subscribed to desired property updates and we are refreshing twin as a result + // of a connection reset or desired property update, send a patch to the downstream device + if (sendDesiredPropertyUpdate) + { + Option> subscriptions = this.connectionManager.GetSubscriptions(id); + subscriptions.ForEach( + s => + { + if (s.TryGetValue(DeviceSubscription.DesiredPropertyUpdates, out bool hasDesiredPropertyUpdatesSubscription) + && hasDesiredPropertyUpdatesSubscription) + { + Events.SendDesiredPropertyUpdateToSubscriber( + id, + t.Twin.Properties.Desired.Version, + cloudTwin.Properties.Desired.Version); + diff = new TwinCollection(JsonEx.Diff(t.Twin.Properties.Desired, cloudTwin.Properties.Desired)); + } + }); + } + } + } + else + { + Events.PreserveCachedTwin( + id, + t.Twin.Properties.Desired.Version, + cloudTwin.Properties.Desired.Version, + t.Twin.Properties.Reported.Version, + cloudTwin.Properties.Reported.Version); + cached = t; + } + + return cached; + }); + } + + if ((diff != null) && (diff.Count != 0)) + { + Events.SendDiffToDeviceProxy(diff.ToString(), id); + IMessage message = this.twinCollectionConverter.ToMessage(diff); + await this.SendDesiredPropertiesToDeviceProxy(id, message); + } + + return cached; + } + + static void ValidatePropertyNameAndLength(string name) + { + if (name != null && Encoding.UTF8.GetByteCount(name) > TwinPropertyValueMaxLength) + { + string truncated = name.Substring(0, 10); + throw new InvalidOperationException($"Length of property name {truncated}.. exceeds maximum length of {TwinPropertyValueMaxLength}"); + } + + // Disabling Possible Null Referece, since name is being tested above. + // ReSharper disable once PossibleNullReferenceException + for (int index = 0; index < name.Length; index++) + { + char ch = name[index]; + // $ is reserved for service properties like $metadata, $version etc. + // However, $ is already a reserved character in Mongo, so we need to substitute it with another character like #. + // So we're also reserving # for service side usage. + if (char.IsControl(ch) || ch == '.' || ch == '$' || ch == '#' || char.IsWhiteSpace(ch)) + { + throw new InvalidOperationException($"Property name {name} contains invalid character '{ch}'"); + } + } + } + + static void ValidatePropertyValueLength(string name, string value) + { + int valueByteCount = value != null ? Encoding.UTF8.GetByteCount(value) : 0; + if (valueByteCount > TwinPropertyValueMaxLength) + { + throw new InvalidOperationException($"Value associated with property name {name} has length {valueByteCount} that exceeds maximum length of {TwinPropertyValueMaxLength}"); + } + } + + [AssertionMethod] + static void ValidateIntegerValue(string name, long value) + { + if (value > TwinPropertyMaxSafeValue || value < TwinPropertyMinSafeValue) + { + throw new InvalidOperationException($"Property {name} has an out of bound value. Valid values are between {TwinPropertyMinSafeValue} and {TwinPropertyMaxSafeValue}"); + } + } + + static void ValidateValueType(string property, JToken value) + { + if (!JsonEx.IsValidToken(value)) + { + throw new InvalidOperationException($"Property {property} has a value of unsupported type. Valid types are integer, float, string, bool, null and nested object"); + } + } + + static void ValidateTwinCollectionSize(TwinCollection collection) + { + long size = Encoding.UTF8.GetByteCount(collection.ToJson()); + if (size > TwinPropertyDocMaxLength) + { + throw new InvalidOperationException($"Twin properties size {size} exceeds maximum {TwinPropertyDocMaxLength}"); + } + } + + static void ValidateTwinProperties(JToken properties, int currentDepth) + { + foreach (JProperty kvp in ((JObject)properties).Properties()) + { + ValidatePropertyNameAndLength(kvp.Name); + + ValidateValueType(kvp.Name, kvp.Value); + + string s = kvp.Value.ToString(); + ValidatePropertyValueLength(kvp.Name, s); + + if ((kvp.Value is JValue) && (kvp.Value.Type is JTokenType.Integer)) + { + ValidateIntegerValue(kvp.Name, (long)kvp.Value); + } + + if ((kvp.Value != null) && (kvp.Value is JObject)) + { + if (currentDepth > TwinPropertyMaxDepth) + { + throw new InvalidOperationException($"Nested depth of twin property exceeds {TwinPropertyMaxDepth}"); + } + + // do validation recursively + ValidateTwinProperties(kvp.Value, currentDepth + 1); + } + } + } + async Task ProcessConnectionEstablishedForDevice(IIdentity identity) { try @@ -100,38 +332,14 @@ await cloudProxy.ForEachAsync( } } - internal void ConnectionEstablishedCallback(object sender, IIdentity identity) - { - Events.ConnectionEstablished(identity.Id); - this.actionBlock.Post(identity); - } - - public async Task GetTwinAsync(string id) - { - return await this.TwinStore.Match( - async (store) => - { - TwinInfo twinInfo = await this.GetTwinInfoWithStoreSupportAsync(id); - return twinInfo.Twin != null - ? this.twinConverter.ToMessage(twinInfo.Twin) - : throw new InvalidOperationException($"Error getting twin for device {id}. Twin is null."); - }, - async () => - { - // pass through to cloud proxy - Option cloudProxy = await this.connectionManager.GetCloudConnection(id); - return await cloudProxy.Match(async (cp) => await cp.GetTwinAsync(), () => throw new InvalidOperationException($"Cloud proxy unavailable for device {id}")); - }); - } - async Task GetTwinInfoWithStoreSupportAsync(string id) { try { Option cloudProxy = await this.connectionManager.GetCloudConnection(id); return await cloudProxy.Map( - cp => this.GetTwinInfoWhenCloudOnlineAsync(id, cp, false) - ).GetOrElse(() => this.GetTwinInfoWhenCloudOfflineAsync(id, new InvalidOperationException($"Error accessing cloud proxy for device {id}"))); + cp => this.GetTwinInfoWhenCloudOnlineAsync(id, cp, false)).GetOrElse( + () => this.GetTwinInfoWhenCloudOfflineAsync(id, new InvalidOperationException($"Error accessing cloud proxy for device {id}"))); } catch (Exception e) { @@ -139,27 +347,6 @@ async Task GetTwinInfoWithStoreSupportAsync(string id) } } - internal async Task ExecuteOnTwinStoreResultAsync(string id, Func twinStoreHit, Func twinStoreMiss) - { - Option cached = await this.TwinStore.Match(s => s.Get(id), () => throw new InvalidOperationException("Missing twin store")); - - if (!cached.HasValue) - { - await twinStoreMiss(); - } - else - { - await cached.ForEachAsync(c => twinStoreHit(c)); - } - } - - public async Task UpdateDesiredPropertiesAsync(string id, IMessage desiredProperties) - { - await this.TwinStore.Map( - s => this.UpdateDesiredPropertiesWithStoreSupportAsync(id, desiredProperties) - ).GetOrElse(() => this.SendDesiredPropertiesToDeviceProxy(id, desiredProperties)); - } - async Task SendDesiredPropertiesToDeviceProxy(string id, IMessage desired) { IDeviceProxy deviceProxy = this.connectionManager.GetDeviceConnection(id) @@ -215,6 +402,7 @@ await twinStore.Update( desired.Version); getTwin = true; } + return new TwinInfo(u.Twin, u.ReportedPropertiesPatch); }); } @@ -237,82 +425,6 @@ async Task UpdateDesiredPropertiesWhenTwinStoreNeedsTwinAsync(string id, TwinCol await this.UpdateDesiredPropertiesWhenTwinStoreHasTwinAsync(id, desired); } - internal async Task GetTwinInfoWhenCloudOnlineAsync(string id, ICloudProxy cp, bool sendDesiredPropertyUpdate) - { - TwinCollection diff = null; - // Used for returning value to caller - TwinInfo cached; - - using (await this.twinLock.LockAsync()) - { - IMessage twinMessage = await cp.GetTwinAsync(); - Twin cloudTwin = this.twinConverter.FromMessage(twinMessage); - Events.GotTwinFromCloudSuccess(id, cloudTwin.Properties.Desired.Version, cloudTwin.Properties.Reported.Version); - var newTwin = new TwinInfo(cloudTwin, null); - cached = newTwin; - - IEntityStore twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); - - await twinStore.PutOrUpdate( - id, - newTwin, - t => - { - // If the new twin is more recent than the cached twin, update the cached copy. - // If not, reject the cloud twin - if (t.Twin == null || - cloudTwin.Properties.Desired.Version > t.Twin.Properties.Desired.Version || - cloudTwin.Properties.Reported.Version > t.Twin.Properties.Reported.Version) - { - if (t.Twin != null) - { - Events.UpdateCachedTwin( - id, - t.Twin.Properties.Desired.Version, - cloudTwin.Properties.Desired.Version, - t.Twin.Properties.Reported.Version, - cloudTwin.Properties.Reported.Version); - cached = new TwinInfo(cloudTwin, t.ReportedPropertiesPatch); - // If the device is subscribed to desired property updates and we are refreshing twin as a result - // of a connection reset or desired property update, send a patch to the downstream device - if (sendDesiredPropertyUpdate) - { - Option> subscriptions = this.connectionManager.GetSubscriptions(id); - subscriptions.ForEach( - s => - { - if (s.TryGetValue(DeviceSubscription.DesiredPropertyUpdates, out bool hasDesiredPropertyUpdatesSubscription) - && hasDesiredPropertyUpdatesSubscription) - { - Events.SendDesiredPropertyUpdateToSubscriber( - id, - t.Twin.Properties.Desired.Version, - cloudTwin.Properties.Desired.Version); - diff = new TwinCollection(JsonEx.Diff(t.Twin.Properties.Desired, cloudTwin.Properties.Desired)); - } - }); - } - } - } - else - { - Events.PreserveCachedTwin(id, - t.Twin.Properties.Desired.Version, cloudTwin.Properties.Desired.Version, - t.Twin.Properties.Reported.Version, cloudTwin.Properties.Reported.Version); - cached = t; - } - return cached; - }); - } - if ((diff != null) && (diff.Count != 0)) - { - Events.SendDiffToDeviceProxy(diff.ToString(), id); - IMessage message = this.twinCollectionConverter.ToMessage(diff); - await this.SendDesiredPropertiesToDeviceProxy(id, message); - } - return cached; - } - async Task GetTwinInfoWhenCloudOfflineAsync(string id, Exception e) { TwinInfo twinInfo = null; @@ -381,23 +493,11 @@ async Task UpdateReportedPropertiesWhenTwinStoreNeedsTwinAsync(string id, TwinCo { // If we fail to find the twin in the twin store, then we simply store the reported property // patch and wait for the next GetTwin or ConnectionEstablished callback to fetch the twin - Events.MissingTwinOnUpdateReported(id, e); throw new TwinNotFoundException("Twin unavailable", e); } - await this.UpdateReportedPropertiesWhenTwinStoreHasTwinAsync(id, reported, cloudVerified); - } - public async Task UpdateReportedPropertiesAsync(string id, IMessage reportedProperties) - { - if (!this.TwinStore.HasValue) - { - await this.SendReportedPropertiesToCloudProxy(id, reportedProperties); - } - else - { - await this.UpdateReportedPropertiesWithStoreSupportAsync(id, reportedProperties); - } + await this.UpdateReportedPropertiesWhenTwinStoreHasTwinAsync(id, reported, cloudVerified); } async Task UpdateReportedPropertiesPatchAsync(string id, TwinInfo newTwinInfo, TwinCollection reportedProperties) @@ -409,15 +509,15 @@ async Task UpdateReportedPropertiesPatchAsync(string id, TwinInfo newTwinInfo, T IEntityStore twinStore = this.TwinStore.Expect(() => new InvalidOperationException("Missing twin store")); await twinStore.PutOrUpdate( - id, - newTwinInfo, - u => - { - string mergedJson = JsonEx.Merge(u.ReportedPropertiesPatch, reportedProperties, /*treatNullAsDelete*/ false); - var mergedPatch = new TwinCollection(mergedJson); - Events.UpdatingReportedPropertiesPatchCollection(id, mergedPatch.Version); - return new TwinInfo(u.Twin, mergedPatch); - }); + id, + newTwinInfo, + u => + { + string mergedJson = JsonEx.Merge(u.ReportedPropertiesPatch, reportedProperties, /*treatNullAsDelete*/ false); + var mergedPatch = new TwinCollection(mergedJson); + Events.UpdatingReportedPropertiesPatchCollection(id, mergedPatch.Version); + return new TwinInfo(u.Twin, mergedPatch); + }); } } catch (Exception e) @@ -440,7 +540,8 @@ async Task UpdateReportedPropertiesWithStoreSupportAsync(string id, IMessage rep // If the reported properties patch is not null, we will not attempt to write the reported // properties to the cloud as we are still waiting for a connection established callback // to sync the local reported properties with that of the cloud - updatePatch = info.Map((ti) => + updatePatch = info.Map( + (ti) => { if (ti.ReportedPropertiesPatch.Count != 0) { @@ -453,7 +554,6 @@ async Task UpdateReportedPropertiesWithStoreSupportAsync(string id, IMessage rep } }).GetOrElse(false); - TwinCollection reported = this.twinCollectionConverter.FromMessage(reportedProperties); if (!updatePatch) @@ -486,7 +586,9 @@ await this.ExecuteOnTwinStoreResultAsync( (t) => this.UpdateReportedPropertiesWhenTwinStoreHasTwinAsync(id, reported, cloudVerified), () => this.UpdateReportedPropertiesWhenTwinStoreNeedsTwinAsync(id, reported, cloudVerified)); } - catch (TwinNotFoundException) { } + catch (TwinNotFoundException) + { + } if (updatePatch) { @@ -512,96 +614,8 @@ async Task SendReportedPropertiesToCloudProxy(string id, IMessage reported) { throw new InvalidOperationException($"Cloud proxy unavailable for device {id}"); } - await cloudProxy.ForEachAsync(cp => cp.UpdateReportedPropertiesAsync(reported)); - } - - static void ValidatePropertyNameAndLength(string name) - { - if (name != null && Encoding.UTF8.GetByteCount(name) > TwinPropertyValueMaxLength) - { - string truncated = name.Substring(0, 10); - throw new InvalidOperationException($"Length of property name {truncated}.. exceeds maximum length of {TwinPropertyValueMaxLength}"); - } - - // Disabling Possible Null Referece, since name is being tested above. - // ReSharper disable once PossibleNullReferenceException - for (int index = 0; index < name.Length; index++) - { - char ch = name[index]; - // $ is reserved for service properties like $metadata, $version etc. - // However, $ is already a reserved character in Mongo, so we need to substitute it with another character like #. - // So we're also reserving # for service side usage. - if (char.IsControl(ch) || ch == '.' || ch == '$' || ch == '#' || char.IsWhiteSpace(ch)) - { - throw new InvalidOperationException($"Property name {name} contains invalid character '{ch}'"); - } - } - } - - static void ValidatePropertyValueLength(string name, string value) - { - int valueByteCount = value != null ? Encoding.UTF8.GetByteCount(value) : 0; - if (valueByteCount > TwinPropertyValueMaxLength) - { - throw new InvalidOperationException($"Value associated with property name {name} has length {valueByteCount} that exceeds maximum length of {TwinPropertyValueMaxLength}"); - } - } - - [AssertionMethod] - static void ValidateIntegerValue(string name, long value) - { - if (value > TwinPropertyMaxSafeValue || value < TwinPropertyMinSafeValue) - { - throw new InvalidOperationException($"Property {name} has an out of bound value. Valid values are between {TwinPropertyMinSafeValue} and {TwinPropertyMaxSafeValue}"); - } - } - - static void ValidateValueType(string property, JToken value) - { - if (!JsonEx.IsValidToken(value)) - { - throw new InvalidOperationException($"Property {property} has a value of unsupported type. Valid types are integer, float, string, bool, null and nested object"); - } - } - - static void ValidateTwinCollectionSize(TwinCollection collection) - { - long size = Encoding.UTF8.GetByteCount(collection.ToJson()); - if (size > TwinPropertyDocMaxLength) - { - throw new InvalidOperationException($"Twin properties size {size} exceeds maximum {TwinPropertyDocMaxLength}"); - } - } - - internal static void ValidateTwinProperties(JToken properties) => ValidateTwinProperties(properties, 1); - - static void ValidateTwinProperties(JToken properties, int currentDepth) - { - foreach (JProperty kvp in ((JObject)properties).Properties()) - { - ValidatePropertyNameAndLength(kvp.Name); - - ValidateValueType(kvp.Name, kvp.Value); - - string s = kvp.Value.ToString(); - ValidatePropertyValueLength(kvp.Name, s); - if ((kvp.Value is JValue) && (kvp.Value.Type is JTokenType.Integer)) - { - ValidateIntegerValue(kvp.Name, (long)kvp.Value); - } - - if ((kvp.Value != null) && (kvp.Value is JObject)) - { - if (currentDepth > TwinPropertyMaxDepth) - { - throw new InvalidOperationException($"Nested depth of twin property exceeds {TwinPropertyMaxDepth}"); - } - - // do validation recursively - ValidateTwinProperties(kvp.Value, currentDepth + 1); - } - } + await cloudProxy.ForEachAsync(cp => cp.UpdateReportedPropertiesAsync(reported)); } // TODO: Move to a Twin helper class (along with Twin manager update). @@ -636,8 +650,8 @@ internal static string EncodeTwinKey(string key) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.TwinManager; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -677,31 +691,41 @@ public static void ReportedPropertiesSyncedToCloudSuccess(string identity, long public static void ValidatedTwinPropertiesSuccess(string id, long version) { - Log.LogDebug((int)EventIds.ValidatedTwinPropertiesSuccess, "Successfully validated reported properties of " + + Log.LogDebug( + (int)EventIds.ValidatedTwinPropertiesSuccess, + "Successfully validated reported properties of " + $"twin with id {id} and reported properties version {version}"); } public static void SentReportedPropertiesToCloud(string id, long version) { - Log.LogDebug((int)EventIds.SentReportedPropertiesToCloud, "Successfully sent reported properties to cloud " + + Log.LogDebug( + (int)EventIds.SentReportedPropertiesToCloud, + "Successfully sent reported properties to cloud " + $"for {id} and reported properties version {version}"); } public static void NeedsUpdateCachedReportedPropertiesPatch(string id, long version) { - Log.LogDebug((int)EventIds.NeedsUpdateCachedReportedPropertiesPatch, "Collective reported properties needs " + + Log.LogDebug( + (int)EventIds.NeedsUpdateCachedReportedPropertiesPatch, + "Collective reported properties needs " + $"update for {id} and reported properties version {version}"); } public static void UpdatingReportedPropertiesPatchCollection(string id, long version) { - Log.LogDebug((int)EventIds.UpdatingReportedPropertiesPatchCollection, "Updating collective reported properties " + + Log.LogDebug( + (int)EventIds.UpdatingReportedPropertiesPatchCollection, + "Updating collective reported properties " + $"patch for {id} at version {version}"); } public static void UpdatedCachedReportedProperties(string id, long reportedVersion, bool cloudVerified) { - Log.LogDebug((int)EventIds.UpdatedCachedReportedProperties, $"Updated cached reported property for {id} " + + Log.LogDebug( + (int)EventIds.UpdatedCachedReportedProperties, + $"Updated cached reported property for {id} " + $"at reported property version {reportedVersion} cloudVerified {cloudVerified}"); } @@ -709,7 +733,9 @@ public static void GetTwinFromStoreWhenOffline(string id, TwinInfo twinInfo, Exc { if (twinInfo.Twin != null) { - Log.LogDebug((int)EventIds.GetTwinFromStoreWhenOffline, $"Getting twin for {id} at desired version " + + Log.LogDebug( + (int)EventIds.GetTwinFromStoreWhenOffline, + $"Getting twin for {id} at desired version " + $"{twinInfo.Twin.Properties.Desired.Version} reported version {twinInfo.Twin.Properties.Reported.Version} from local store. Get from cloud threw {e.GetType()} {e.Message}"); } else @@ -720,26 +746,34 @@ public static void GetTwinFromStoreWhenOffline(string id, TwinInfo twinInfo, Exc public static void GotTwinFromCloudSuccess(string id, long desiredVersion, long reportedVersion) { - Log.LogDebug((int)EventIds.GotTwinFromCloudSuccess, $"Successfully got twin for {id} from cloud at " + + Log.LogDebug( + (int)EventIds.GotTwinFromCloudSuccess, + $"Successfully got twin for {id} from cloud at " + $"desired version {desiredVersion} reported version {reportedVersion}"); } public static void UpdateCachedTwin(string id, long cachedDesired, long cloudDesired, long cachedReported, long cloudReported) { - Log.LogDebug((int)EventIds.UpdateCachedTwin, $"Updating cached twin for {id} from " + + Log.LogDebug( + (int)EventIds.UpdateCachedTwin, + $"Updating cached twin for {id} from " + $"desired version {cachedDesired} to {cloudDesired} and reported version {cachedReported} to " + $"{cloudReported}"); } public static void SendDesiredPropertyUpdateToSubscriber(string id, long oldDesiredVersion, long cloudDesiredVersion) { - Log.LogDebug((int)EventIds.SendDesiredPropertyUpdateToSubscriber, $"Sending desired property update for {id}" + + Log.LogDebug( + (int)EventIds.SendDesiredPropertyUpdateToSubscriber, + $"Sending desired property update for {id}" + $" old desired version {oldDesiredVersion} cloud desired version {cloudDesiredVersion}"); } public static void PreserveCachedTwin(string id, long cachedDesired, long cloudDesired, long cachedReported, long cloudReported) { - Log.LogDebug((int)EventIds.PreserveCachedTwin, $"Local twin for {id} at higher or equal desired version " + + Log.LogDebug( + (int)EventIds.PreserveCachedTwin, + $"Local twin for {id} at higher or equal desired version " + $"{cachedDesired} compared to cloud {cloudDesired} or reported version {cachedReported} compared to cloud" + $" {cloudReported}"); } @@ -771,14 +805,18 @@ public static void SentDesiredPropertiesToDevice(string id, long version) public static void InOrderDesiredPropertyPatchReceived(string id, long from, long to) { - Log.LogDebug((int)EventIds.InOrderDesiredPropertyPatchReceived, "In order desired property patch" + - $" from {from} to {to} for device {id}"); + Log.LogDebug( + (int)EventIds.InOrderDesiredPropertyPatchReceived, + "In order desired property patch" + + $" from {from} to {to} for device {id}"); } public static void OutOfOrderDesiredPropertyPatchReceived(string id, long from, long to) { - Log.LogDebug((int)EventIds.OutOfOrderDesiredPropertyPatchReceived, "Out of order desired property patch" + - $" from {from} to {to} for device {id}"); + Log.LogDebug( + (int)EventIds.OutOfOrderDesiredPropertyPatchReceived, + "Out of order desired property patch" + + $" from {from} to {to} for device {id}"); } public static void ConnectionEstablishedCallbackException(string id, Exception e) @@ -792,20 +830,25 @@ public static void ConnectionEstablishedCallbackException(string id, Exception e else { Log.LogWarning( - (int)EventIds.ConnectionEstablishedCallbackException, e, + (int)EventIds.ConnectionEstablishedCallbackException, + e, $"Error in connection established callback for client {id}"); } } public static void MissingTwinOnUpdateReported(string id, Exception e) { - Log.LogDebug((int)EventIds.MissingTwinOnUpdateReported, $"Failed to find twin for {id}" + + Log.LogDebug( + (int)EventIds.MissingTwinOnUpdateReported, + $"Failed to find twin for {id}" + $" while updating reported properties with error {e.Message}"); } public static void UpdateReportedPropertiesFailed(string id, Exception e) { - Log.LogWarning((int)EventIds.UpdateReportedPropertiesFailed, "Failed to update reported " + + Log.LogWarning( + (int)EventIds.UpdateReportedPropertiesFailed, + "Failed to update reported " + $" properties for {id} with error {e.Message}"); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinNotFoundException.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinNotFoundException.cs index 7b2605df64a..632ea606f11 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinNotFoundException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/TwinNotFoundException.cs @@ -7,10 +7,12 @@ public class TwinNotFoundException : Exception { public TwinNotFoundException(string message) : this(message, null) - { } + { + } public TwinNotFoundException(string message, Exception innerException) - : base(message, innerException) - { } + : base(message, innerException) + { + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/CloudListener.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/CloudListener.cs index fc36a189302..3c6d4079af1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/CloudListener.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/CloudListener.cs @@ -5,20 +5,20 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Cloud using Microsoft.Azure.Devices.Edge.Util; public class CloudListener : ICloudListener - { - readonly IEdgeHub edgeHub; - readonly string clientId; + { + readonly IEdgeHub edgeHub; + readonly string clientId; - public CloudListener(IEdgeHub edgeHub, string clientId) - { - this.edgeHub = Preconditions.CheckNotNull(edgeHub, nameof(edgeHub)); - this.clientId = Preconditions.CheckNotNull(clientId, nameof(clientId)); - } + public CloudListener(IEdgeHub edgeHub, string clientId) + { + this.edgeHub = Preconditions.CheckNotNull(edgeHub, nameof(edgeHub)); + this.clientId = Preconditions.CheckNotNull(clientId, nameof(clientId)); + } - public Task CallMethodAsync(DirectMethodRequest request) => this.edgeHub.InvokeMethodAsync(this.clientId, request); + public Task CallMethodAsync(DirectMethodRequest request) => this.edgeHub.InvokeMethodAsync(this.clientId, request); - public Task OnDesiredPropertyUpdates(IMessage desiredProperties) => this.edgeHub.UpdateDesiredPropertiesAsync(this.clientId, desiredProperties); + public Task OnDesiredPropertyUpdates(IMessage desiredProperties) => this.edgeHub.UpdateDesiredPropertiesAsync(this.clientId, desiredProperties); - public Task ProcessMessageAsync(IMessage message) => this.edgeHub.SendC2DMessageAsync(this.clientId, message); - } + public Task ProcessMessageAsync(IMessage message) => this.edgeHub.SendC2DMessageAsync(this.clientId, message); + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/ICloudProxy.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/ICloudProxy.cs index 6f6a5dd5da6..b50600e40e4 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/ICloudProxy.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/cloud/ICloudProxy.cs @@ -9,7 +9,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Cloud /// connected to the edge hub. Objects implementing this interface essentially /// use the IoT Hub Module Client/Device Client to open and maintain a connection to the /// module’s counterpart in Azure IoT Hub. - /// /// There is exactly one instance of a cloud proxy object for each device that /// is connected to the edge hub. The /// object is responsible for creating and maintaining instances of ICloudProxy diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/ConfigUpdater.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/ConfigUpdater.cs index 40cb46a5ac7..6111dc0d81e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/ConfigUpdater.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/ConfigUpdater.cs @@ -38,11 +38,12 @@ public async Task Init(IConfigSource configProvider) } else { - await edgeHubConfig.ForEachAsync(async ehc => - { - await this.UpdateRoutes(ehc.Routes, false); - this.UpdateStoreAndForwardConfig(ehc.StoreAndForwardConfiguration); - }); + await edgeHubConfig.ForEachAsync( + async ehc => + { + await this.UpdateRoutes(ehc.Routes, false); + this.UpdateStoreAndForwardConfig(ehc.StoreAndForwardConfiguration); + }); Events.Initialized(); } } @@ -88,6 +89,7 @@ async Task UpdateRoutes(IEnumerable<(string Name, string Value, Route Route)> ro await this.router.SetRoute(route); } } + Events.RoutesUpdated(routesList); } } @@ -103,8 +105,8 @@ void UpdateStoreAndForwardConfig(StoreAndForwardConfiguration storeAndForwardCon static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.ConfigUpdater; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -124,13 +126,17 @@ internal static void Initialized() internal static void InitializingError(Exception ex) { - Log.LogError((int)EventIds.InitializeError, ex, + Log.LogError( + (int)EventIds.InitializeError, + ex, FormattableString.Invariant($"Error initializing edge hub configuration")); } internal static void UpdateError(Exception ex) { - Log.LogError((int)EventIds.UpdateError, ex, + Log.LogError( + (int)EventIds.UpdateError, + ex, FormattableString.Invariant($"Error updating edge hub configuration")); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/DeviceConnectionStatus.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/DeviceConnectionStatus.cs index 09d30365d10..0ea489a70be 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/DeviceConnectionStatus.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/DeviceConnectionStatus.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Config using Newtonsoft.Json; using Newtonsoft.Json.Converters; - internal class DeviceConnectionStatus + class DeviceConnectionStatus { public DeviceConnectionStatus(ConnectionStatus status, DateTime? lastConnectedTimeUtc, DateTime? lastDisconnectedTimeUtc) { @@ -24,5 +24,5 @@ public DeviceConnectionStatus(ConnectionStatus status, DateTime? lastConnectedTi [JsonProperty(PropertyName = "lastDisconnectedTimeUtc", NullValueHandling = NullValueHandling.Ignore)] public DateTime? LastDisconnectTimeUtc { get; } - } + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/LocalConfigSource.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/LocalConfigSource.cs index a936855170b..c2bc2decc6a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/LocalConfigSource.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/config/LocalConfigSource.cs @@ -2,11 +2,12 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Config { using System; - using System.Linq; using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Routing.Core; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Core.Constants; public class LocalConfigSource : IConfigSource { @@ -18,11 +19,13 @@ public LocalConfigSource(RouteFactory routeFactory, IDictionary Preconditions.CheckNotNull(routes, nameof(routes)); Preconditions.CheckNotNull(storeAndForwardConfiguration, nameof(storeAndForwardConfiguration)); IEnumerable<(string Name, string Value, Route Route)> parsedRoutes = routes.Select(r => (r.Key, r.Value, routeFactory.Create(r.Value))); - this.edgeHubConfig = new EdgeHubConfig(Core.Constants.ConfigSchemaVersion.ToString(), parsedRoutes, storeAndForwardConfiguration); + this.edgeHubConfig = new EdgeHubConfig(Constants.ConfigSchemaVersion.ToString(), parsedRoutes, storeAndForwardConfiguration); } public Task> GetConfig() => Task.FromResult(Option.Some(this.edgeHubConfig)); - public void SetConfigUpdatedCallback(Func callback) { } + public void SetConfigUpdatedCallback(Func callback) + { + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/DeviceMessageHandler.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/DeviceMessageHandler.cs index 5febf5f6140..6c2e9d83517 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/DeviceMessageHandler.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/DeviceMessageHandler.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Device class DeviceMessageHandler : IDeviceListener, IDeviceProxy { + const int GenericBadRequest = 400000; static readonly TimeSpan MessageResponseTimeout = TimeSpan.FromSeconds(30); readonly ConcurrentDictionary> methodCallTaskCompletionSources = new ConcurrentDictionary>(); @@ -25,9 +26,6 @@ class DeviceMessageHandler : IDeviceListener, IDeviceProxy readonly AsyncLock serializeMessagesLock = new AsyncLock(); IDeviceProxy underlyingProxy; - // IoTHub error codes - const int GenericBadRequest = 400000; - public DeviceMessageHandler(IIdentity identity, IEdgeHub edgeHub, IConnectionManager connectionManager) { this.Identity = Preconditions.CheckNotNull(identity, nameof(identity)); @@ -49,7 +47,7 @@ public Task ProcessMethodResponseAsync(IMessage message) if (this.methodCallTaskCompletionSources.TryRemove(correlationId.ToLowerInvariant(), out TaskCompletionSource taskCompletion)) { DirectMethodResponse directMethodResponse = !message.Properties.TryGetValue(SystemProperties.StatusCode, out string statusCode) - || !int.TryParse(statusCode, out int statusCodeValue) + || !int.TryParse(statusCode, out int statusCodeValue) ? new DirectMethodResponse(correlationId, null, GenericBadRequest) : new DirectMethodResponse(correlationId, message.Body, statusCodeValue); @@ -122,11 +120,12 @@ public async Task AddDesiredPropertyUpdatesSubscription(string correlationId) if (!string.IsNullOrWhiteSpace(correlationId)) { IMessage responseMessage = new EdgeMessage.Builder(new byte[0]) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = correlationId, - [SystemProperties.StatusCode] = ((int)HttpStatusCode.OK).ToString() - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = correlationId, + [SystemProperties.StatusCode] = ((int)HttpStatusCode.OK).ToString() + }) .Build(); await this.SendTwinUpdate(responseMessage); } @@ -138,11 +137,12 @@ public async Task RemoveDesiredPropertyUpdatesSubscription(string correlationId) if (!string.IsNullOrWhiteSpace(correlationId)) { IMessage responseMessage = new EdgeMessage.Builder(new byte[0]) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = correlationId, - [SystemProperties.StatusCode] = ((int)HttpStatusCode.OK).ToString() - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = correlationId, + [SystemProperties.StatusCode] = ((int)HttpStatusCode.OK).ToString() + }) .Build(); await this.SendTwinUpdate(responseMessage); } @@ -193,12 +193,13 @@ public async Task UpdateReportedPropertiesAsync(IMessage reportedPropertiesMessa if (!string.IsNullOrWhiteSpace(correlationId)) { IMessage responseMessage = new EdgeMessage.Builder(new byte[0]) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = correlationId, - [SystemProperties.EnqueuedTime] = DateTime.UtcNow.ToString("o"), - [SystemProperties.StatusCode] = ((int)HttpStatusCode.NoContent).ToString() - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = correlationId, + [SystemProperties.EnqueuedTime] = DateTime.UtcNow.ToString("o"), + [SystemProperties.StatusCode] = ((int)HttpStatusCode.NoContent).ToString() + }) .Build(); await this.SendTwinUpdate(responseMessage); } @@ -219,95 +220,22 @@ async Task HandleTwinOperationException(string correlationId, Exception e) : (int)HttpStatusCode.InternalServerError; IMessage responseMessage = new EdgeMessage.Builder(new byte[0]) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = correlationId, - [SystemProperties.EnqueuedTime] = DateTime.UtcNow.ToString("o"), - [SystemProperties.StatusCode] = statusCode.ToString() - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = correlationId, + [SystemProperties.EnqueuedTime] = DateTime.UtcNow.ToString("o"), + [SystemProperties.StatusCode] = statusCode.ToString() + }) .Build(); await this.SendTwinUpdate(responseMessage); } } - #region IDeviceProxy - - public Task SendC2DMessageAsync(IMessage message) => this.underlyingProxy.SendC2DMessageAsync(message); - - /// - /// This method sends the message to the device, and adds the TaskCompletionSource (that awaits the response) to the messageTaskCompletionSources list. - /// When the message feedback call comes back, ProcessMessageFeedback sets the TaskCompletionSource value, which results in the awaiting task to be completed. - /// If no response comes back, then it times out. - /// - public async Task SendMessageAsync(IMessage message, string input) - { - // Locking here since multiple queues could be sending to the same module - // The messages need to be processed in order. - using (await this.serializeMessagesLock.LockAsync()) - { - string lockToken = Guid.NewGuid().ToString(); - message.SystemProperties[SystemProperties.LockToken] = lockToken; - - var taskCompletionSource = new TaskCompletionSource(); - this.messageTaskCompletionSources.TryAdd(lockToken, taskCompletionSource); - - Events.SendingMessage(this.Identity, lockToken); - await this.underlyingProxy.SendMessageAsync(message, input); - - Task completedTask = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(MessageResponseTimeout)); - if (completedTask != taskCompletionSource.Task) - { - Events.MessageFeedbackTimedout(this.Identity, lockToken); - taskCompletionSource.SetException(new TimeoutException("Message completion response not received")); - this.messageTaskCompletionSources.TryRemove(lockToken, out taskCompletionSource); - } - - await taskCompletionSource.Task; - } - } - - /// - /// This method invokes the method on the device, and adds the TaskCompletionSource (that awaits the response) to the methodCallTaskCompletionSources list. - /// When the response comes back, SendMethodResponse sets the TaskCompletionSource value, which results in the awaiting task to be completed. - /// If no response comes back, then it times out. - /// - public async Task InvokeMethodAsync(DirectMethodRequest request) - { - var taskCompletion = new TaskCompletionSource(); - - this.methodCallTaskCompletionSources.TryAdd(request.CorrelationId.ToLowerInvariant(), taskCompletion); - await this.underlyingProxy.InvokeMethodAsync(request); - Events.MethodCallSentToClient(this.Identity, request.Id, request.CorrelationId); - - Task completedTask = await Task.WhenAny(taskCompletion.Task, Task.Delay(request.ResponseTimeout)); - if (completedTask != taskCompletion.Task) - { - Events.MethodResponseTimedout(this.Identity, request.Id, request.CorrelationId); - taskCompletion.TrySetResult(new DirectMethodResponse(new EdgeHubTimeoutException($"Timed out waiting for device to respond to method request {request.CorrelationId}"), HttpStatusCode.GatewayTimeout)); - this.methodCallTaskCompletionSources.TryRemove(request.CorrelationId.ToLowerInvariant(), out taskCompletion); - } - - return await taskCompletion.Task; - } - - public Task OnDesiredPropertyUpdates(IMessage twinUpdates) => this.underlyingProxy.OnDesiredPropertyUpdates(twinUpdates); - - public Task SendTwinUpdate(IMessage twin) => this.underlyingProxy.SendTwinUpdate(twin); - - public Task CloseAsync(Exception ex) => this.underlyingProxy.CloseAsync(ex); - - public void SetInactive() => this.underlyingProxy.SetInactive(); - - public bool IsActive => this.underlyingProxy.IsActive; - - public Task> GetUpdatedIdentity() => this.underlyingProxy.GetUpdatedIdentity(); - - #endregion - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.DeviceListener; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -397,5 +325,79 @@ public static void ProcessedGetTwin(string identityId) Log.LogDebug((int)EventIds.ProcessedGetTwin, Invariant($"Processed GetTwin for {identityId}")); } } + + #region IDeviceProxy + + public Task SendC2DMessageAsync(IMessage message) => this.underlyingProxy.SendC2DMessageAsync(message); + + /// + /// This method sends the message to the device, and adds the TaskCompletionSource (that awaits the response) to the messageTaskCompletionSources list. + /// When the message feedback call comes back, ProcessMessageFeedback sets the TaskCompletionSource value, which results in the awaiting task to be completed. + /// If no response comes back, then it times out. + /// + public async Task SendMessageAsync(IMessage message, string input) + { + // Locking here since multiple queues could be sending to the same module + // The messages need to be processed in order. + using (await this.serializeMessagesLock.LockAsync()) + { + string lockToken = Guid.NewGuid().ToString(); + message.SystemProperties[SystemProperties.LockToken] = lockToken; + + var taskCompletionSource = new TaskCompletionSource(); + this.messageTaskCompletionSources.TryAdd(lockToken, taskCompletionSource); + + Events.SendingMessage(this.Identity, lockToken); + await this.underlyingProxy.SendMessageAsync(message, input); + + Task completedTask = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(MessageResponseTimeout)); + if (completedTask != taskCompletionSource.Task) + { + Events.MessageFeedbackTimedout(this.Identity, lockToken); + taskCompletionSource.SetException(new TimeoutException("Message completion response not received")); + this.messageTaskCompletionSources.TryRemove(lockToken, out taskCompletionSource); + } + + await taskCompletionSource.Task; + } + } + + /// + /// This method invokes the method on the device, and adds the TaskCompletionSource (that awaits the response) to the methodCallTaskCompletionSources list. + /// When the response comes back, SendMethodResponse sets the TaskCompletionSource value, which results in the awaiting task to be completed. + /// If no response comes back, then it times out. + /// + public async Task InvokeMethodAsync(DirectMethodRequest request) + { + var taskCompletion = new TaskCompletionSource(); + + this.methodCallTaskCompletionSources.TryAdd(request.CorrelationId.ToLowerInvariant(), taskCompletion); + await this.underlyingProxy.InvokeMethodAsync(request); + Events.MethodCallSentToClient(this.Identity, request.Id, request.CorrelationId); + + Task completedTask = await Task.WhenAny(taskCompletion.Task, Task.Delay(request.ResponseTimeout)); + if (completedTask != taskCompletion.Task) + { + Events.MethodResponseTimedout(this.Identity, request.Id, request.CorrelationId); + taskCompletion.TrySetResult(new DirectMethodResponse(new EdgeHubTimeoutException($"Timed out waiting for device to respond to method request {request.CorrelationId}"), HttpStatusCode.GatewayTimeout)); + this.methodCallTaskCompletionSources.TryRemove(request.CorrelationId.ToLowerInvariant(), out taskCompletion); + } + + return await taskCompletion.Task; + } + + public Task OnDesiredPropertyUpdates(IMessage twinUpdates) => this.underlyingProxy.OnDesiredPropertyUpdates(twinUpdates); + + public Task SendTwinUpdate(IMessage twin) => this.underlyingProxy.SendTwinUpdate(twin); + + public Task CloseAsync(Exception ex) => this.underlyingProxy.CloseAsync(ex); + + public void SetInactive() => this.underlyingProxy.SetInactive(); + + public bool IsActive => this.underlyingProxy.IsActive; + + public Task> GetUpdatedIdentity() => this.underlyingProxy.GetUpdatedIdentity(); + + #endregion } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceListener.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceListener.cs index 540eb85c783..5b7a5454335 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceListener.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceListener.cs @@ -7,6 +7,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Device public interface IDeviceListener { + IIdentity Identity { get; } + Task ProcessDeviceMessageAsync(IMessage message); Task ProcessDeviceMessageBatchAsync(IEnumerable message); @@ -23,8 +25,6 @@ public interface IDeviceListener Task ProcessMessageFeedbackAsync(string messageId, FeedbackStatus feedbackStatus); - IIdentity Identity { get; } - Task AddSubscription(DeviceSubscription subscription); Task RemoveSubscription(DeviceSubscription subscription); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceProxy.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceProxy.cs index d996ba27863..96346b50519 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceProxy.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/device/IDeviceProxy.cs @@ -13,13 +13,16 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Device /// object for each device that is connected to the edge hub. The /// object is responsible for creating and maintaining instances of IDeviceProxy /// for every connecting device. - /// /// In the MQTT implementation for example the implementation of this interface uses /// the protocol gateway library to interface with MQTT clients by transforming messages /// between MQTT packets and objects. /// public interface IDeviceProxy { + bool IsActive { get; } + + IIdentity Identity { get; } + Task CloseAsync(Exception ex); Task SendC2DMessageAsync(IMessage message); @@ -32,10 +35,6 @@ public interface IDeviceProxy Task SendTwinUpdate(IMessage twin); - bool IsActive { get; } - - IIdentity Identity { get; } - void SetInactive(); Task> GetUpdatedIdentity(); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ICertificateCredentials.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ICertificateCredentials.cs index 2c34c15a039..755795bd47e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ICertificateCredentials.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ICertificateCredentials.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Identity public interface ICertificateCredentials : IClientCredentials { X509Certificate2 ClientCertificate { get; } + IList ClientCertificateChain { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ModuleIdentity.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ModuleIdentity.cs index f018ef91b38..f69cbf2ca51 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ModuleIdentity.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/ModuleIdentity.cs @@ -9,7 +9,8 @@ public class ModuleIdentity : Identity, IModuleIdentity { readonly Lazy asString; - public ModuleIdentity(string iotHubHostName, + public ModuleIdentity( + string iotHubHostName, string deviceId, string moduleId) : base(iotHubHostName) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceAuthentication.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceAuthentication.cs index 0c6c9d407a2..8dd52d9096d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceAuthentication.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceAuthentication.cs @@ -7,7 +7,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Identity.Service using Newtonsoft.Json; public class ServiceAuthentication : IEquatable - { public ServiceAuthentication(SymmetricKeyAuthentication symmetricKeyAuthentication) : this(ServiceAuthenticationType.SymmetricKey, Preconditions.CheckNotNull(symmetricKeyAuthentication, nameof(symmetricKeyAuthentication)), null) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentity.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentity.cs index f974568ffec..90dea128950 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentity.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentity.cs @@ -78,7 +78,7 @@ public override int GetHashCode() { unchecked { - int hashCode = (this.Id != null ? this.Id.GetHashCode() : 0); + int hashCode = this.Id != null ? this.Id.GetHashCode() : 0; hashCode = (hashCode * 397) ^ (this.DeviceId != null ? this.DeviceId.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.ModuleId.GetHashCode(); hashCode = (hashCode * 397) ^ (this.Capabilities != null ? this.Capabilities.GetHashCode() : 0); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentityStatus.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentityStatus.cs index c1d602d89a5..229be9b46c6 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentityStatus.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/ServiceIdentityStatus.cs @@ -10,6 +10,7 @@ public enum ServiceIdentityStatus { [EnumMember(Value = "enabled")] Enabled, + [EnumMember(Value = "disabled")] Disabled } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/SymmetricKeyAuthentication.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/SymmetricKeyAuthentication.cs index 60a1fe09e24..e607359ca7b 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/SymmetricKeyAuthentication.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/identity/service/SymmetricKeyAuthentication.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Identity.Service using Newtonsoft.Json; public class SymmetricKeyAuthentication : IEquatable - { [JsonConstructor] public SymmetricKeyAuthentication(string primaryKey, string secondaryKey) @@ -24,11 +23,20 @@ public SymmetricKeyAuthentication(string primaryKey, string secondaryKey) public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) + { return false; + } + if (ReferenceEquals(this, obj)) + { return true; + } + if (obj.GetType() != this.GetType()) + { return false; + } + return this.Equals((SymmetricKeyAuthentication)obj); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/CloudEndpoint.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/CloudEndpoint.cs index c3aef919052..9e72eb10ce3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/CloudEndpoint.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/CloudEndpoint.cs @@ -17,12 +17,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Routing using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Extensions.Logging; using static System.FormattableString; - using Endpoint = Microsoft.Azure.Devices.Routing.Core.Endpoint; using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; - using IProcessor = Microsoft.Azure.Devices.Routing.Core.IProcessor; using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; - using ISinkResult = Microsoft.Azure.Devices.Routing.Core.ISinkResult; - using Option = Microsoft.Azure.Devices.Edge.Util.Option; + using ISinkResult = Microsoft.Azure.Devices.Routing.Core.ISinkResult; + using Option = Microsoft.Azure.Devices.Routing.Core.Util.Option; using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; public class CloudEndpoint : Endpoint @@ -63,6 +61,10 @@ public CloudMessageProcessor(CloudEndpoint endpoint) this.cloudEndpoint = Preconditions.CheckNotNull(endpoint); } + public Endpoint Endpoint => this.cloudEndpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(this.IsTransientException); + public async Task ProcessAsync(IRoutingMessage routingMessage, CancellationToken token) { Preconditions.CheckNotNull(routingMessage, nameof(routingMessage)); @@ -94,37 +96,39 @@ await identity.ForEachAsync( } else { - await cloudProxy.ForEachAsync(async cp => - { - try + await cloudProxy.ForEachAsync( + async cp => { - using (Metrics.CloudLatency(id)) + try { - await cp.SendMessageAsync(message); - } - succeeded.Add(routingMessage); - Metrics.MessageCount(id); - } - catch (Exception ex) - { - if (IsRetryable(ex)) - { - failed.Add(routingMessage); - } - else - { - Events.InvalidMessage(ex); - invalid.Add(new InvalidDetails(routingMessage, FailureKind.InvalidInput)); - sendFailureDetails = new SendFailureDetails(FailureKind.InvalidInput, ex); - } + using (Metrics.CloudLatency(id)) + { + await cp.SendMessageAsync(message); + } - if (failed.Count > 0) + succeeded.Add(routingMessage); + Metrics.MessageCount(id); + } + catch (Exception ex) { - Events.RetryingMessage(routingMessage, ex); - sendFailureDetails = new SendFailureDetails(FailureKind.Transient, new EdgeHubIOException($"Error sending messages to IotHub for device {this.cloudEndpoint.Id}")); + if (IsRetryable(ex)) + { + failed.Add(routingMessage); + } + else + { + Events.InvalidMessage(ex); + invalid.Add(new InvalidDetails(routingMessage, FailureKind.InvalidInput)); + sendFailureDetails = new SendFailureDetails(FailureKind.InvalidInput, ex); + } + + if (failed.Count > 0) + { + Events.RetryingMessage(routingMessage, ex); + sendFailureDetails = new SendFailureDetails(FailureKind.Transient, new EdgeHubIOException($"Error sending messages to IotHub for device {this.cloudEndpoint.Id}")); + } } - } - }); + }); } }); } @@ -139,7 +143,7 @@ public async Task ProcessAsync(ICollection routing var failed = new List(); var invalid = new List>(); Devices.Routing.Core.Util.Option sendFailureDetails = - Devices.Routing.Core.Util.Option.None(); + Option.None(); Events.ProcessingMessages(routingMessages); foreach (IRoutingMessage routingMessage in routingMessages) @@ -156,7 +160,10 @@ public async Task ProcessAsync(ICollection routing sendFailureDetails = res.SendFailureDetails; } - return new SinkResult(succeeded, failed, invalid, + return new SinkResult( + succeeded, + failed, + invalid, sendFailureDetails.GetOrElse(null)); } @@ -166,9 +173,7 @@ public Task CloseAsync(CancellationToken token) return TaskEx.Done; } - public Endpoint Endpoint => this.cloudEndpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(this.IsTransientException); + static bool IsRetryable(Exception ex) => ex != null && RetryableExceptions.Contains(ex.GetType()); bool IsTransientException(Exception ex) => ex is EdgeHubIOException || ex is EdgeHubConnectionException; @@ -176,47 +181,21 @@ Util.Option GetIdentity(IRoutingMessage routingMessage) { if (routingMessage.SystemProperties.TryGetValue(SystemProperties.ConnectionDeviceId, out string deviceId)) { - return Option.Some(routingMessage.SystemProperties.TryGetValue(SystemProperties.ConnectionModuleId, out string moduleId) - ? $"{deviceId}/{moduleId}" - : deviceId); + return Util.Option.Some( + routingMessage.SystemProperties.TryGetValue(SystemProperties.ConnectionModuleId, out string moduleId) + ? $"{deviceId}/{moduleId}" + : deviceId); } - Events.DeviceIdNotFound(routingMessage); - return Option.None(); - } - - static bool IsRetryable(Exception ex) => ex != null && RetryableExceptions.Contains(ex.GetType()); - } - static class Metrics - { - static readonly CounterOptions EdgeHubToCloudMessageCountOptions = new CounterOptions - { - Name = "EdgeHubToCloudMessageSentCount", - MeasurementUnit = Unit.Events, - ResetOnReporting = true, - }; - static readonly TimerOptions EdgeHubToCloudMessageLatencyOptions = new TimerOptions - { - Name = "EdgeHubToCloudMessageLatencyMs", - MeasurementUnit = Unit.None, - DurationUnit = TimeUnit.Milliseconds, - RateUnit = TimeUnit.Seconds - }; - - static MetricTags GetTags(string id) - { - return new MetricTags("DeviceId", id); + Events.DeviceIdNotFound(routingMessage); + return Util.Option.None(); } - - public static void MessageCount(string identity) => Util.Metrics.CountIncrement(GetTags(identity), EdgeHubToCloudMessageCountOptions, 1); - - public static IDisposable CloudLatency(string identity) => Util.Metrics.Latency(GetTags(identity), EdgeHubToCloudMessageLatencyOptions); } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.CloudEndpoint; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -236,6 +215,16 @@ public static void DeviceIdNotFound(IRoutingMessage routingMessage) Log.LogWarning((int)EventIds.DeviceIdNotFound, message); } + public static void ProcessingMessages(ICollection routingMessages) + { + Log.LogDebug((int)EventIds.ProcessingMessages, Invariant($"Sending {routingMessages.Count} message(s) upstream.")); + } + + public static void InvalidMessageNoIdentity() + { + Log.LogWarning((int)EventIds.InvalidMessageNoIdentity, "Cannot process message with no identity, discarding it."); + } + internal static void IoTHubNotConnected(string id) { Log.LogWarning((int)EventIds.IoTHubNotConnected, Invariant($"Could not get an active Iot Hub connection for device {id}")); @@ -263,15 +252,32 @@ internal static void InvalidMessage(Exception ex) // TODO - Add more info to this log message Log.LogWarning((int)EventIds.InvalidMessage, ex, Invariant($"Non retryable exception occurred while sending message.")); } + } - public static void ProcessingMessages(ICollection routingMessages) + static class Metrics + { + static readonly CounterOptions EdgeHubToCloudMessageCountOptions = new CounterOptions { - Log.LogDebug((int)EventIds.ProcessingMessages, Invariant($"Sending {routingMessages.Count} message(s) upstream.")); - } + Name = "EdgeHubToCloudMessageSentCount", + MeasurementUnit = Unit.Events, + ResetOnReporting = true, + }; - public static void InvalidMessageNoIdentity() + static readonly TimerOptions EdgeHubToCloudMessageLatencyOptions = new TimerOptions { - Log.LogWarning((int)EventIds.InvalidMessageNoIdentity, "Cannot process message with no identity, discarding it."); + Name = "EdgeHubToCloudMessageLatencyMs", + MeasurementUnit = Unit.None, + DurationUnit = TimeUnit.Milliseconds, + RateUnit = TimeUnit.Seconds + }; + + public static void MessageCount(string identity) => Util.Metrics.CountIncrement(GetTags(identity), EdgeHubToCloudMessageCountOptions, 1); + + public static IDisposable CloudLatency(string identity) => Util.Metrics.Latency(GetTags(identity), EdgeHubToCloudMessageLatencyOptions); + + static MetricTags GetTags(string id) + { + return new MetricTags("DeviceId", id); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/EndpointFactory.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/EndpointFactory.cs index 231fa5d5554..ff4f47dd5df 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/EndpointFactory.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/EndpointFactory.cs @@ -17,8 +17,10 @@ public class EndpointFactory : IEndpointFactory readonly string edgeDeviceId; readonly ConcurrentDictionary cache; - public EndpointFactory(IConnectionManager connectionManager, - Core.IMessageConverter messageConverter, string edgeDeviceId) + public EndpointFactory( + IConnectionManager connectionManager, + Core.IMessageConverter messageConverter, + string edgeDeviceId) { this.connectionManager = Preconditions.CheckNotNull(connectionManager, nameof(connectionManager)); this.messageConverter = Preconditions.CheckNotNull(messageConverter, nameof(messageConverter)); @@ -45,7 +47,7 @@ public Endpoint CreateFunctionEndpoint(string function, string parameterString) throw new InvalidOperationException($"Function endpoint type '{function ?? string.Empty}' not supported."); } - // Parameter string contains endpoint address in this format - /modules/{mid}/inputs/{input}. + // Parameter string contains endpoint address in this format - /modules/{mid}/inputs/{input}. parameterString = Preconditions.CheckNonWhiteSpace(parameterString, nameof(parameterString)).Trim(); if (parameterString.StartsWith("/")) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/ModuleEndpoint.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/ModuleEndpoint.cs index 0d2af35d1ac..3d6afd07357 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/ModuleEndpoint.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/ModuleEndpoint.cs @@ -13,13 +13,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Routing using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Extensions.Logging; using static System.FormattableString; - using Endpoint = Microsoft.Azure.Devices.Routing.Core.Endpoint; using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; - using IProcessor = Microsoft.Azure.Devices.Routing.Core.IProcessor; using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; - using ISinkResult = Microsoft.Azure.Devices.Routing.Core.ISinkResult; + using ISinkResult = Microsoft.Azure.Devices.Routing.Core.ISinkResult; using Option = Microsoft.Azure.Devices.Edge.Util.Option; - using TaskEx = Microsoft.Azure.Devices.Edge.Util.TaskEx; public class ModuleEndpoint : Endpoint { @@ -38,15 +35,62 @@ public ModuleEndpoint(string id, string moduleId, string input, IConnectionManag public override string Type => this.GetType().Name; - public override IProcessor CreateProcessor() => new ModuleMessageProcessor(this); - public string Input { get; } + public override IProcessor CreateProcessor() => new ModuleMessageProcessor(this); + public override void LogUserMetrics(long messageCount, long latencyInMs) { // TODO - No-op } + static class Events + { + const int IdStart = HubCoreEventIds.ModuleEndpoint; + static readonly ILogger Log = Logger.Factory.CreateLogger(); + + enum EventIds + { + NoDeviceProxy = IdStart, + ErrorSendingMessages, + RetryingMessages, + InvalidMessage, + ProcessingMessages + } + + public static void NoDeviceProxy(ModuleEndpoint moduleEndpoint) + { + Log.LogWarning((int)EventIds.NoDeviceProxy, Invariant($"Module {moduleEndpoint.moduleId} is not connected")); + } + + public static void ErrorSendingMessages(ModuleEndpoint moduleEndpoint, Exception ex) + { + Log.LogWarning((int)EventIds.ErrorSendingMessages, ex, Invariant($"Error sending messages to module {moduleEndpoint.moduleId}")); + } + + public static void ProcessingMessages(ModuleEndpoint moduleEndpoint, ICollection routingMessages) + { + Log.LogDebug((int)EventIds.ProcessingMessages, Invariant($"Sending {routingMessages.Count} message(s) to module {moduleEndpoint.moduleId}.")); + } + + public static void NoMessagesSubscription(string moduleId) + { + Log.LogWarning((int)EventIds.NoDeviceProxy, Invariant($"No subscription for receiving messages found for {moduleId}")); + } + + internal static void RetryingMessages(int count, string endpointId) + { + // TODO - Add more info to this log message + Log.LogDebug((int)EventIds.RetryingMessages, Invariant($"Retrying {count} messages to {endpointId}.")); + } + + internal static void InvalidMessage(Exception ex) + { + // TODO - Add more info to this log message + Log.LogWarning((int)EventIds.InvalidMessage, ex, Invariant($"Non retryable exception occurred while sending message.")); + } + } + class ModuleMessageProcessor : IProcessor { static readonly ISet RetryableExceptions = new HashSet @@ -56,14 +100,18 @@ class ModuleMessageProcessor : IProcessor typeof(EdgeHubIOException) }; - Util.Option devicePoxy = Option.None(); readonly ModuleEndpoint moduleEndpoint; + Util.Option devicePoxy = Option.None(); public ModuleMessageProcessor(ModuleEndpoint endpoint) { this.moduleEndpoint = Preconditions.CheckNotNull(endpoint); } + public Endpoint Endpoint => this.moduleEndpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(this.IsTransientException); + public Task ProcessAsync(IRoutingMessage routingMessage, CancellationToken token) { return this.ProcessAsync(new[] { Preconditions.CheckNotNull(routingMessage, nameof(routingMessage)) }, token); @@ -91,27 +139,29 @@ public async Task ProcessAsync(ICollection routing foreach (IRoutingMessage routingMessage in routingMessages) { IMessage message = this.moduleEndpoint.messageConverter.ToMessage(routingMessage); - await deviceProxy.ForEachAsync(async dp => - { - try + await deviceProxy.ForEachAsync( + async dp => { - await dp.SendMessageAsync(message, this.moduleEndpoint.Input); - succeeded.Add(routingMessage); - } - catch (Exception ex) - { - if (IsRetryable(ex)) + try { - failed.Add(routingMessage); + await dp.SendMessageAsync(message, this.moduleEndpoint.Input); + succeeded.Add(routingMessage); } - else + catch (Exception ex) { - Events.InvalidMessage(ex); - invalid.Add(new InvalidDetails(routingMessage, FailureKind.None)); + if (IsRetryable(ex)) + { + failed.Add(routingMessage); + } + else + { + Events.InvalidMessage(ex); + invalid.Add(new InvalidDetails(routingMessage, FailureKind.None)); + } + + Events.ErrorSendingMessages(this.moduleEndpoint, ex); } - Events.ErrorSendingMessages(this.moduleEndpoint, ex); - } - }); + }); } if (failed.Count > 0) @@ -130,12 +180,10 @@ public Task CloseAsync(CancellationToken token) return TaskEx.Done; } - public Endpoint Endpoint => this.moduleEndpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(this.IsTransientException); + static bool IsRetryable(Exception ex) => ex != null && RetryableExceptions.Contains(ex.GetType()); bool IsTransientException(Exception ex) => ex is EdgeHubConnectionException - || ex is EdgeHubIOException; + || ex is EdgeHubIOException; Util.Option GetDeviceProxy() { @@ -162,60 +210,12 @@ Util.Option GetDeviceProxy() { Events.NoDeviceProxy(this.moduleEndpoint); } + return Option.None(); }); return this.devicePoxy; } - - static bool IsRetryable(Exception ex) => ex != null && RetryableExceptions.Contains(ex.GetType()); - } - - static class Events - { - static readonly ILogger Log = Logger.Factory.CreateLogger(); - const int IdStart = HubCoreEventIds.ModuleEndpoint; - - enum EventIds - { - NoDeviceProxy = IdStart, - ErrorSendingMessages, - RetryingMessages, - InvalidMessage, - ProcessingMessages - } - - public static void NoDeviceProxy(ModuleEndpoint moduleEndpoint) - { - Log.LogWarning((int)EventIds.NoDeviceProxy, Invariant($"Module {moduleEndpoint.moduleId} is not connected")); - } - - public static void ErrorSendingMessages(ModuleEndpoint moduleEndpoint, Exception ex) - { - Log.LogWarning((int)EventIds.ErrorSendingMessages, ex, Invariant($"Error sending messages to module {moduleEndpoint.moduleId}")); - } - - internal static void RetryingMessages(int count, string endpointId) - { - // TODO - Add more info to this log message - Log.LogDebug((int)EventIds.RetryingMessages, Invariant($"Retrying {count} messages to {endpointId}.")); - } - - internal static void InvalidMessage(Exception ex) - { - // TODO - Add more info to this log message - Log.LogWarning((int)EventIds.InvalidMessage, ex, Invariant($"Non retryable exception occurred while sending message.")); - } - - public static void ProcessingMessages(ModuleEndpoint moduleEndpoint, ICollection routingMessages) - { - Log.LogDebug((int)EventIds.ProcessingMessages, Invariant($"Sending {routingMessages.Count} message(s) to module {moduleEndpoint.moduleId}.")); - } - - public static void NoMessagesSubscription(string moduleId) - { - Log.LogWarning((int)EventIds.NoDeviceProxy, Invariant($"No subscription for receiving messages found for {moduleId}")); - } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingEdgeHub.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingEdgeHub.cs index 9c793381d6b..f2eb7318d4e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingEdgeHub.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingEdgeHub.cs @@ -10,17 +10,20 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Routing using App.Metrics.Timer; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; + using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Extensions.Logging; using Serilog.Events; using static System.FormattableString; - using IIdentity = Microsoft.Azure.Devices.Edge.Hub.Core.Identity.IIdentity; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Core.Constants; using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; + using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; public class RoutingEdgeHub : IEdgeHub { + const long MaxMessageSize = 256 * 1024; // matches IoTHub readonly Router router; readonly Core.IMessageConverter messageConverter; readonly IConnectionManager connectionManager; @@ -28,10 +31,12 @@ public class RoutingEdgeHub : IEdgeHub readonly string edgeDeviceId; readonly IInvokeMethodHandler invokeMethodHandler; - const long MaxMessageSize = 256 * 1024; // matches IoTHub - - public RoutingEdgeHub(Router router, Core.IMessageConverter messageConverter, - IConnectionManager connectionManager, ITwinManager twinManager, string edgeDeviceId, + public RoutingEdgeHub( + Router router, + Core.IMessageConverter messageConverter, + IConnectionManager connectionManager, + ITwinManager twinManager, + string edgeDeviceId, IInvokeMethodHandler invokeMethodHandler, IDeviceConnectivityManager deviceConnectivityManager) { @@ -105,41 +110,6 @@ public Task SendC2DMessageAsync(string id, IMessage message) return deviceProxy.ForEachAsync(d => d.SendC2DMessageAsync(message)); } - static void ValidateMessageSize(IRoutingMessage messageToBeValidated) - { - long messageSize = messageToBeValidated.Size(); - if (messageSize > MaxMessageSize) - { - throw new EdgeHubMessageTooLargeException($"Message size is {messageSize} bytes which is greater than the max size {MaxMessageSize} bytes allowed"); - } - } - - IRoutingMessage ProcessMessageInternal(IMessage message, bool validateSize) - { - this.AddEdgeSystemProperties(message); - IRoutingMessage routingMessage = this.messageConverter.FromMessage(Preconditions.CheckNotNull(message, nameof(message))); - - // Validate message size - if (validateSize) - { - ValidateMessageSize(routingMessage); - } - - return routingMessage; - } - - internal void AddEdgeSystemProperties(IMessage message) - { - message.SystemProperties[Core.SystemProperties.EdgeMessageId] = Guid.NewGuid().ToString(); - if (message.SystemProperties.TryGetValue(Core.SystemProperties.ConnectionDeviceId, out string deviceId)) - { - string edgeHubOriginInterface = deviceId == this.edgeDeviceId - ? Core.Constants.InternalOriginInterface - : Core.Constants.DownstreamOriginInterface; - message.SystemProperties[Core.SystemProperties.EdgeHubOriginInterface] = edgeHubOriginInterface; - } - } - public Task GetTwinAsync(string id) { Events.GetTwinCallReceived(id); @@ -182,6 +152,32 @@ public async Task RemoveSubscription(string id, DeviceSubscription deviceSubscri } } + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.router?.Dispose(); + } + } + + internal void AddEdgeSystemProperties(IMessage message) + { + message.SystemProperties[SystemProperties.EdgeMessageId] = Guid.NewGuid().ToString(); + if (message.SystemProperties.TryGetValue(SystemProperties.ConnectionDeviceId, out string deviceId)) + { + string edgeHubOriginInterface = deviceId == this.edgeDeviceId + ? Constants.InternalOriginInterface + : Constants.DownstreamOriginInterface; + message.SystemProperties[SystemProperties.EdgeHubOriginInterface] = edgeHubOriginInterface; + } + } + internal async Task ProcessSubscription(string id, Option cloudProxy, DeviceSubscription deviceSubscription, bool addSubscription) { Events.ProcessingSubscription(id, deviceSubscription); @@ -192,6 +188,7 @@ internal async Task ProcessSubscription(string id, Option cloudProx { cloudProxy.ForEach(c => c.StartListening()); } + break; case DeviceSubscription.DesiredPropertyUpdates: @@ -208,6 +205,7 @@ internal async Task ProcessSubscription(string id, Option cloudProx { await cloudProxy.ForEachAsync(c => c.RemoveCallMethodAsync()); } + break; case DeviceSubscription.ModuleMessages: @@ -218,6 +216,29 @@ internal async Task ProcessSubscription(string id, Option cloudProx } } + static void ValidateMessageSize(IRoutingMessage messageToBeValidated) + { + long messageSize = messageToBeValidated.Size(); + if (messageSize > MaxMessageSize) + { + throw new EdgeHubMessageTooLargeException($"Message size is {messageSize} bytes which is greater than the max size {MaxMessageSize} bytes allowed"); + } + } + + IRoutingMessage ProcessMessageInternal(IMessage message, bool validateSize) + { + this.AddEdgeSystemProperties(message); + IRoutingMessage routingMessage = this.messageConverter.FromMessage(Preconditions.CheckNotNull(message, nameof(message))); + + // Validate message size + if (validateSize) + { + ValidateMessageSize(routingMessage); + } + + return routingMessage; + } + async void DeviceConnected(object sender, EventArgs eventArgs) { Events.DeviceConnectedProcessingSubscriptions(); @@ -257,50 +278,10 @@ await subscriptions.ForEachAsync( }); } - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.router?.Dispose(); - } - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - static class Metrics - { - static readonly CounterOptions EdgeHubMessageReceivedCountOptions = new CounterOptions - { - Name = "EdgeHubMessageReceivedCount", - MeasurementUnit = Unit.Events, - ResetOnReporting = true, - }; - static readonly TimerOptions EdgeHubMessageLatencyOptions = new TimerOptions - { - Name = "EdgeHubMessageLatencyMs", - MeasurementUnit = Unit.None, - DurationUnit = TimeUnit.Milliseconds, - RateUnit = TimeUnit.Seconds - }; - - internal static MetricTags GetTags(IIdentity identity) - { - return new MetricTags("Id", identity.Id); - } - - public static void MessageCount(IIdentity identity, long count) => Util.Metrics.CountIncrement(GetTags(identity), EdgeHubMessageReceivedCountOptions, count); - - public static IDisposable MessageLatency(IIdentity identity) => Util.Metrics.Latency(GetTags(identity), EdgeHubMessageLatencyOptions); - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.RoutingEdgeHub; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -323,33 +304,6 @@ public static void MethodCallReceived(string fromId, string toId, string correla Log.LogDebug((int)EventIds.MethodReceived, Invariant($"Received method invoke call from {fromId} for {toId} with correlation ID {correlationId}")); } - internal static void MessageReceived(IIdentity identity, IMessage message) - { - if (message.SystemProperties.TryGetValue(SystemProperties.MessageId, out string messageId)) - { - Log.LogDebug((int)EventIds.MessageReceived, Invariant($"Received message from {identity.Id} with message Id {messageId}")); - } - else - { - Log.LogDebug((int)EventIds.MessageReceived, Invariant($"Received message from {identity.Id}")); - } - } - - internal static void UpdateReportedPropertiesReceived(IIdentity identity) - { - Log.LogDebug((int)EventIds.ReportedPropertiesUpdateReceived, Invariant($"Reported properties update message received from {identity.Id}")); - } - - internal static void GetTwinCallReceived(string id) - { - Log.LogDebug((int)EventIds.MessageReceived, Invariant($"GetTwin call received from {id ?? string.Empty}")); - } - - internal static void UpdateDesiredPropertiesCallReceived(string id) - { - Log.LogDebug((int)EventIds.DesiredPropertiesUpdateReceived, Invariant($"Desired properties update message received for {id ?? string.Empty}")); - } - public static void UnableToSendC2DMessageNoDeviceConnection(string id) { Log.LogWarning((int)EventIds.DeviceConnectionNotFound, Invariant($"Unable to send C2D message to device {id} as an active device connection was not found.")); @@ -411,22 +365,12 @@ public static void ProcessingSubscription(string id, DeviceSubscription deviceSu Log.LogInformation((int)EventIds.ProcessingSubscription, Invariant($"Processing subscription {deviceSubscription} for client {id}.")); } - internal static void DeviceConnectedProcessingSubscriptions() - { - Log.LogInformation((int)EventIds.ProcessingSubscription, Invariant($"Device connected to cloud, processing subscriptions for connected clients.")); - } - - internal static void ErrorProcessingSubscriptions(Exception e) - { - Log.LogWarning((int)EventIds.ProcessingSubscription, e, Invariant($"Error processing subscriptions for connected clients.")); - } - public static void MessagesReceived(IIdentity identity, IList messages) { if (Logger.GetLogLevel() <= LogEventLevel.Debug) { string messageIdsString = messages - .Select(m => m.SystemProperties.TryGetValue(SystemProperties.MessageId, out string messageId) ? messageId : string.Empty) + .Select(m => m.SystemProperties.TryGetValue(Devices.Routing.Core.SystemProperties.MessageId, out string messageId) ? messageId : string.Empty) .Where(m => !string.IsNullOrWhiteSpace(m)) .Join(", "); @@ -440,6 +384,70 @@ public static void MessagesReceived(IIdentity identity, IList messages } } } + + internal static void MessageReceived(IIdentity identity, IMessage message) + { + if (message.SystemProperties.TryGetValue(Devices.Routing.Core.SystemProperties.MessageId, out string messageId)) + { + Log.LogDebug((int)EventIds.MessageReceived, Invariant($"Received message from {identity.Id} with message Id {messageId}")); + } + else + { + Log.LogDebug((int)EventIds.MessageReceived, Invariant($"Received message from {identity.Id}")); + } + } + + internal static void UpdateReportedPropertiesReceived(IIdentity identity) + { + Log.LogDebug((int)EventIds.ReportedPropertiesUpdateReceived, Invariant($"Reported properties update message received from {identity.Id}")); + } + + internal static void GetTwinCallReceived(string id) + { + Log.LogDebug((int)EventIds.MessageReceived, Invariant($"GetTwin call received from {id ?? string.Empty}")); + } + + internal static void UpdateDesiredPropertiesCallReceived(string id) + { + Log.LogDebug((int)EventIds.DesiredPropertiesUpdateReceived, Invariant($"Desired properties update message received for {id ?? string.Empty}")); + } + + internal static void DeviceConnectedProcessingSubscriptions() + { + Log.LogInformation((int)EventIds.ProcessingSubscription, Invariant($"Device connected to cloud, processing subscriptions for connected clients.")); + } + + internal static void ErrorProcessingSubscriptions(Exception e) + { + Log.LogWarning((int)EventIds.ProcessingSubscription, e, Invariant($"Error processing subscriptions for connected clients.")); + } + } + + static class Metrics + { + static readonly CounterOptions EdgeHubMessageReceivedCountOptions = new CounterOptions + { + Name = "EdgeHubMessageReceivedCount", + MeasurementUnit = Unit.Events, + ResetOnReporting = true, + }; + + static readonly TimerOptions EdgeHubMessageLatencyOptions = new TimerOptions + { + Name = "EdgeHubMessageLatencyMs", + MeasurementUnit = Unit.None, + DurationUnit = TimeUnit.Milliseconds, + RateUnit = TimeUnit.Seconds + }; + + public static void MessageCount(IIdentity identity, long count) => Util.Metrics.CountIncrement(GetTags(identity), EdgeHubMessageReceivedCountOptions, count); + + public static IDisposable MessageLatency(IIdentity identity) => Util.Metrics.Latency(GetTags(identity), EdgeHubMessageLatencyOptions); + + internal static MetricTags GetTags(IIdentity identity) + { + return new MetricTags("Id", identity.Id); + } } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingMessageConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingMessageConverter.cs index b30eda99fa8..cf3be5f4e3f 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingMessageConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/routing/RoutingMessageConverter.cs @@ -5,11 +5,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Routing using System.Collections.Generic; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; - using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; using RoutingMessage = Microsoft.Azure.Devices.Routing.Core.Message; - public class RoutingMessageConverter : Core.IMessageConverter + public class RoutingMessageConverter : IMessageConverter { public IMessage ToMessage(IRoutingMessage routingMessage) { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/CheckpointStore.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/CheckpointStore.cs index c97e84fccb0..bfeeb747584 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/CheckpointStore.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/CheckpointStore.cs @@ -5,11 +5,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Storage using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Storage; + using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Newtonsoft.Json; - using Option = Microsoft.Azure.Devices.Routing.Core.Util.Option; public class CheckpointStore : ICheckpointStore { @@ -60,16 +59,18 @@ public async Task SetCheckpointDataAsync(string id, CheckpointData checkpointDat internal static CheckpointEntity GetCheckpointEntity(CheckpointData checkpointData) { - return new CheckpointEntity(checkpointData.Offset, + return new CheckpointEntity( + checkpointData.Offset, checkpointData.LastFailedRevivalTime.Match(v => v, () => (DateTime?)null), checkpointData.UnhealthySince.Match(v => v, () => (DateTime?)null)); } internal static CheckpointData GetCheckpointData(CheckpointEntity checkpointEntity) { - return new CheckpointData(checkpointEntity.Offset, - checkpointEntity.LastFailedRevivalTime.HasValue ? Option.Some(checkpointEntity.LastFailedRevivalTime.Value) : Option.None(), - checkpointEntity.UnhealthySince.HasValue ? Option.Some(checkpointEntity.UnhealthySince.Value) : Option.None()); + return new CheckpointData( + checkpointEntity.Offset, + checkpointEntity.LastFailedRevivalTime.HasValue ? Devices.Routing.Core.Util.Option.Some(checkpointEntity.LastFailedRevivalTime.Value) : Devices.Routing.Core.Util.Option.None(), + checkpointEntity.UnhealthySince.HasValue ? Devices.Routing.Core.Util.Option.Some(checkpointEntity.UnhealthySince.Value) : Devices.Routing.Core.Util.Option.None()); } internal class CheckpointEntity diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/MessageStore.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/MessageStore.cs index 8dd17bf497e..8c632ad7184 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/MessageStore.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Core/storage/MessageStore.cs @@ -17,6 +17,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Storage using Newtonsoft.Json; using static System.FormattableString; using Constants = Microsoft.Azure.Devices.Edge.Hub.Core.Constants; + using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; /// /// This object is responsible for storing messages for each endpoint. @@ -32,8 +33,8 @@ public class MessageStore : IMessageStore readonly CleanupProcessor messagesCleaner; readonly ICheckpointStore checkpointStore; readonly IStoreProvider storeProvider; + readonly long messageCount = 0; TimeSpan timeToLive; - long messageCount = 0; public MessageStore(IStoreProvider storeProvider, ICheckpointStore checkpointStore, TimeSpan timeToLive) { @@ -79,7 +80,7 @@ public async Task Add(string endpointId, IMessage message) throw new InvalidOperationException($"SequentialStore for endpoint {nameof(endpointId)} not found"); } - if (!message.SystemProperties.TryGetValue(Core.SystemProperties.EdgeMessageId, out string edgeMessageId)) + if (!message.SystemProperties.TryGetValue(SystemProperties.EdgeMessageId, out string edgeMessageId)) { throw new InvalidOperationException("Message does not contain required system property EdgeMessageId"); } @@ -89,15 +90,18 @@ public async Task Add(string endpointId, IMessage message) // entity store. // Note - if we fail to add the message to the sequential store (for some reason), then we will end up not cleaning up the message in the // entity store. But that should be rare enough that it might be okay. Also it is better than not being able to forward the message. - // Alternative is to add retry logic to the pump, but that is more complicated, and could affect performance. + // Alternative is to add retry logic to the pump, but that is more complicated, and could affect performance. // TODO - Need to support transactions for these operations. The underlying storage layers support it. using (Metrics.MessageStoreLatency(endpointId)) { - await this.messageEntityStore.PutOrUpdate(edgeMessageId, new MessageWrapper(message), (m) => - { - m.RefCount++; - return m; - }); + await this.messageEntityStore.PutOrUpdate( + edgeMessageId, + new MessageWrapper(message), + (m) => + { + m.RefCount++; + return m; + }); } try @@ -132,87 +136,19 @@ public IMessageIterator GetMessageIterator(string endpointId, long startingOffse public IMessageIterator GetMessageIterator(string endpointId) => this.GetMessageIterator(endpointId, DefaultStartingOffset); - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.messageEntityStore?.Dispose(); - this.messagesCleaner?.Dispose(); - Events.DisposingMessageStore(); - } - } - public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } - class MessageIterator : IMessageIterator + protected virtual void Dispose(bool disposing) { - long startingOffset; - readonly IKeyValueStore entityStore; - readonly ISequentialStore endpointSequentialStore; - - public MessageIterator(IKeyValueStore entityStore, - ISequentialStore endpointSequentialStore, - long startingOffset) - { - this.entityStore = entityStore; - this.endpointSequentialStore = endpointSequentialStore; - this.startingOffset = startingOffset < 0 ? 0 : startingOffset; - } - - public async Task> GetNext(int batchSize) - { - Preconditions.CheckRange(batchSize, 1, nameof(batchSize)); - var messageList = new List(); - - try - { - Events.GettingNextBatch(this.endpointSequentialStore.EntityName, this.startingOffset, batchSize); - // TODO - Currently, this does not iterate over a snapshot. This should work as the cleanup and reference counting is managed at - // application level. But need to check if creating a snapshot for iterating is needed. - List<(long offset, MessageRef msgRef)> batch = (await this.endpointSequentialStore.GetBatch(this.startingOffset, batchSize)) - .ToList(); - if (batch.Count > 0) - { - foreach ((long offset, MessageRef msgRef) item in batch) - { - Option messageWrapper = await this.entityStore.Get(item.msgRef.EdgeMessageId); - if (!messageWrapper.HasValue) - { - Events.MessageNotFound(item.msgRef.EdgeMessageId); - } - else - { - messageWrapper - .Map(m => this.AddMessageOffset(m.Message, item.offset)) - .ForEach(m => messageList.Add(m)); - } - } - - this.startingOffset = batch[batch.Count - 1].offset + 1; - } - Events.ObtainedNextBatch(this.endpointSequentialStore.EntityName, this.startingOffset, messageList.Count); - } - catch (Exception e) - { - Events.ErrorGettingMessagesBatch(this.endpointSequentialStore.EntityName, e); - } - return messageList; - } - - IMessage AddMessageOffset(IMessage message, long offset) + if (disposing) { - return new Message( - message.MessageSource, - message.Body, - message.Properties.ToDictionary(), - message.SystemProperties.ToDictionary(), - offset, - message.EnqueuedTime, - message.DequeuedTime); + this.messageEntityStore?.Dispose(); + this.messagesCleaner?.Dispose(); + Events.DisposingMessageStore(); } } @@ -226,14 +162,6 @@ public MessageWrapper(IMessage message) { } - [JsonConstructor] - // Disabling this warning since we use this constructor on Deserialization. - // ReSharper disable once UnusedMember.Local - MessageWrapper(Message message, DateTime timeStamp, int refCount) - : this((IMessage)message, timeStamp, refCount) - { - } - public MessageWrapper(IMessage message, DateTime timeStamp, int refCount) { Preconditions.CheckArgument(timeStamp != default(DateTime)); @@ -242,34 +170,19 @@ public MessageWrapper(IMessage message, DateTime timeStamp, int refCount) this.RefCount = Preconditions.CheckRange(refCount, 0, nameof(refCount)); } - public IMessage Message { get; } - - public DateTime TimeStamp { get; } - - public int RefCount { get; set; } - } - - /// - /// Class that stores references to stored messages. This is used for maintaining endpoint queues - /// - class MessageRef - { - public MessageRef(string edgeMessageId) - : this(edgeMessageId, DateTime.UtcNow) - { - } - [JsonConstructor] - public MessageRef(string edgeMessageId, DateTime timeStamp) + // Disabling this warning since we use this constructor on Deserialization. + // ReSharper disable once UnusedMember.Local + MessageWrapper(Message message, DateTime timeStamp, int refCount) + : this((IMessage)message, timeStamp, refCount) { - Preconditions.CheckArgument(timeStamp != default(DateTime)); - this.EdgeMessageId = Preconditions.CheckNonWhiteSpace(edgeMessageId, nameof(edgeMessageId)); - this.TimeStamp = timeStamp; } - public string EdgeMessageId { get; } + public IMessage Message { get; } public DateTime TimeStamp { get; } + + public int RefCount { get; set; } } class CleanupProcessor : IDisposable @@ -288,6 +201,15 @@ public CleanupProcessor(MessageStore messageStore) this.cancellationTokenSource = new CancellationTokenSource(); } + public void Dispose() + { + this.ensureCleanupTaskTimer?.Dispose(); + this.cancellationTokenSource?.Cancel(); + // wait for 30 secs for the cleanup task to finish. + this.cleanupTask?.Wait(TimeSpan.FromSeconds(30)); + // Not disposing the cleanup task, in case it is not completed yet. + } + void EnsureCleanupTask(object state) { if (this.cleanupTask == null || this.cleanupTask.IsCompleted) @@ -298,10 +220,10 @@ void EnsureCleanupTask(object state) } /// - /// Messages need to be cleaned up in the following scenarios – - /// 1. When the message expires (exceeds TTL) - /// 2. When a message has been processed (indicated by the the checkpoint) - /// 3. When there are 0 references to a message in the store from the message queues, + /// Messages need to be cleaned up in the following scenarios – + /// 1.When the message expires (exceeds TTL) + /// 2.When a message has been processed (indicated by the the checkpoint) + /// 3.When there are 0 references to a message in the store from the message queues, /// the message itself is deleted from the store (this means the message was successfully delivered to all endpoints). /// // TODO - Update cleanup logic to cleanup expired messages from entity store as well. /// @@ -331,6 +253,7 @@ async Task CleanupMessages() ISequentialStore sequentialStore = endpointSequentialStore.Value; Events.CleanupCheckpointState(endpointSequentialStore.Key, checkpointData); int cleanupEntityStoreCount = 0; + async Task DeleteMessageCallback(long offset, MessageRef messageRef) { if (checkpointData.Offset < offset && @@ -341,7 +264,7 @@ async Task DeleteMessageCallback(long offset, MessageRef messageRef) bool deleteMessage = false; - // Decrement ref count. + // Decrement ref count. await this.messageStore.messageEntityStore.Update( messageRef.EdgeMessageId, m => @@ -350,10 +273,12 @@ await this.messageStore.messageEntityStore.Update( { m.RefCount--; } + if (m.RefCount == 0) { deleteMessage = true; } + return m; }); @@ -382,6 +307,7 @@ await this.messageStore.messageEntityStore.Update( Events.ErrorCleaningMessagesForEndpoint(ex, endpointSequentialStore.Key); } } + await Task.Delay(cleanupTaskSleepTime); } } @@ -391,49 +317,12 @@ await this.messageStore.messageEntityStore.Update( throw; } } - - public void Dispose() - { - this.ensureCleanupTaskTimer?.Dispose(); - this.cancellationTokenSource?.Cancel(); - // wait for 30 secs for the cleanup task to finish. - this.cleanupTask?.Wait(TimeSpan.FromSeconds(30)); - // Not disposing the cleanup task, in case it is not completed yet. - } - } - - static class Metrics - { - static readonly TimerOptions MessageEntityStorePutOrUpdateLatencyOptions = new TimerOptions - { - Name = "MessageEntityStorePutOrUpdateLatencyMs", - MeasurementUnit = Unit.None, - DurationUnit = TimeUnit.Milliseconds, - RateUnit = TimeUnit.Seconds - }; - - static readonly TimerOptions SequentialStoreAppendLatencyOptions = new TimerOptions - { - Name = "SequentialStoreAppendLatencyMs", - MeasurementUnit = Unit.None, - DurationUnit = TimeUnit.Milliseconds, - RateUnit = TimeUnit.Seconds - }; - - internal static MetricTags GetTags(string id) - { - return new MetricTags("EndpointId", id); - } - - public static IDisposable MessageStoreLatency(string identity) => Edge.Util.Metrics.Latency(GetTags(identity), MessageEntityStorePutOrUpdateLatencyOptions); - - public static IDisposable SequentialStoreLatency(string identity) => Edge.Util.Metrics.Latency(GetTags(identity), SequentialStoreAppendLatencyOptions); } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HubCoreEventIds.MessageStore; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -489,6 +378,11 @@ public static void CleanupCompleted(string endpointId, int queueMessagesCount, i Log.LogDebug((int)EventIds.CleanupCompleted, Invariant($"Total messages cleaned up from queue for endpoint {endpointId} = {totalQueueMessagesCount}, and total messages cleaned up for message store = {totalStoreMessagesCount}.")); } + public static void ErrorGettingMessagesBatch(string entityName, Exception ex) + { + Log.LogWarning((int)EventIds.ErrorGettingMessagesBatch, ex, $"Error getting next batch for endpoint {entityName}."); + } + internal static void TtlUpdated(TimeSpan timeSpan) { Log.LogInformation((int)EventIds.TtlUpdated, $"Updated message store TTL to {timeSpan.TotalSeconds} seconds"); @@ -532,10 +426,127 @@ internal static void MessageAdded(long offset, string edgeMessageId, string endp Log.LogDebug((int)EventIds.MessageAdded, Invariant($"Added message {edgeMessageId} to store for {endpointId} at offset {offset} - messageCount = {messageCount}")); } } + } - public static void ErrorGettingMessagesBatch(string entityName, Exception ex) + class MessageIterator : IMessageIterator + { + readonly IKeyValueStore entityStore; + readonly ISequentialStore endpointSequentialStore; + long startingOffset; + + public MessageIterator( + IKeyValueStore entityStore, + ISequentialStore endpointSequentialStore, + long startingOffset) { - Log.LogWarning((int)EventIds.ErrorGettingMessagesBatch, ex, $"Error getting next batch for endpoint {entityName}."); + this.entityStore = entityStore; + this.endpointSequentialStore = endpointSequentialStore; + this.startingOffset = startingOffset < 0 ? 0 : startingOffset; + } + + public async Task> GetNext(int batchSize) + { + Preconditions.CheckRange(batchSize, 1, nameof(batchSize)); + var messageList = new List(); + + try + { + Events.GettingNextBatch(this.endpointSequentialStore.EntityName, this.startingOffset, batchSize); + // TODO - Currently, this does not iterate over a snapshot. This should work as the cleanup and reference counting is managed at + // application level. But need to check if creating a snapshot for iterating is needed. + List<(long offset, MessageRef msgRef)> batch = (await this.endpointSequentialStore.GetBatch(this.startingOffset, batchSize)) + .ToList(); + if (batch.Count > 0) + { + foreach ((long offset, MessageRef msgRef) item in batch) + { + Option messageWrapper = await this.entityStore.Get(item.msgRef.EdgeMessageId); + if (!messageWrapper.HasValue) + { + Events.MessageNotFound(item.msgRef.EdgeMessageId); + } + else + { + messageWrapper + .Map(m => this.AddMessageOffset(m.Message, item.offset)) + .ForEach(m => messageList.Add(m)); + } + } + + this.startingOffset = batch[batch.Count - 1].offset + 1; + } + + Events.ObtainedNextBatch(this.endpointSequentialStore.EntityName, this.startingOffset, messageList.Count); + } + catch (Exception e) + { + Events.ErrorGettingMessagesBatch(this.endpointSequentialStore.EntityName, e); + } + + return messageList; + } + + IMessage AddMessageOffset(IMessage message, long offset) + { + return new Message( + message.MessageSource, + message.Body, + message.Properties.ToDictionary(), + message.SystemProperties.ToDictionary(), + offset, + message.EnqueuedTime, + message.DequeuedTime); + } + } + + /// + /// Class that stores references to stored messages. This is used for maintaining endpoint queues + /// + class MessageRef + { + public MessageRef(string edgeMessageId) + : this(edgeMessageId, DateTime.UtcNow) + { + } + + [JsonConstructor] + public MessageRef(string edgeMessageId, DateTime timeStamp) + { + Preconditions.CheckArgument(timeStamp != default(DateTime)); + this.EdgeMessageId = Preconditions.CheckNonWhiteSpace(edgeMessageId, nameof(edgeMessageId)); + this.TimeStamp = timeStamp; + } + + public string EdgeMessageId { get; } + + public DateTime TimeStamp { get; } + } + + static class Metrics + { + static readonly TimerOptions MessageEntityStorePutOrUpdateLatencyOptions = new TimerOptions + { + Name = "MessageEntityStorePutOrUpdateLatencyMs", + MeasurementUnit = Unit.None, + DurationUnit = TimeUnit.Milliseconds, + RateUnit = TimeUnit.Seconds + }; + + static readonly TimerOptions SequentialStoreAppendLatencyOptions = new TimerOptions + { + Name = "SequentialStoreAppendLatencyMs", + MeasurementUnit = Unit.None, + DurationUnit = TimeUnit.Milliseconds, + RateUnit = TimeUnit.Seconds + }; + + public static IDisposable MessageStoreLatency(string identity) => Util.Metrics.Latency(GetTags(identity), MessageEntityStorePutOrUpdateLatencyOptions); + + public static IDisposable SequentialStoreLatency(string identity) => Util.Metrics.Latency(GetTags(identity), SequentialStoreAppendLatencyOptions); + + internal static MetricTags GetTags(string id) + { + return new MetricTags("EndpointId", id); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Adapters/HttpsExtensionConnectionAdapter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Adapters/HttpsExtensionConnectionAdapter.cs index dbd5e6a37c1..371f0a1a2a0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Adapters/HttpsExtensionConnectionAdapter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Adapters/HttpsExtensionConnectionAdapter.cs @@ -3,6 +3,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Adapters { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net.Security; @@ -20,10 +21,11 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Adapters // https://github.com/aspnet/HttpAbstractions/issues/808 public class HttpsExtensionConnectionAdapter : IConnectionAdapter { + internal const string DisableHandshakeTimeoutSwitch = "Switch.Microsoft.AspNetCore.Server.Kestrel.Https.DisableHandshakeTimeout"; + // See http://oid-info.com/get/1.3.6.1.5.5.7.3.1 // Indicates that a certificate can be used as a SSL server certificate const string ServerAuthenticationOid = "1.3.6.1.5.5.7.3.1"; - internal const string DisableHandshakeTimeoutSwitch = "Switch.Microsoft.AspNetCore.Server.Kestrel.Https.DisableHandshakeTimeout"; static readonly TimeSpan HandshakeTimeout = TimeSpan.FromSeconds(10); static readonly ClosedAdaptedConnection ClosedAdaptedConnectionInstance = new ClosedAdaptedConnection(); readonly HttpsConnectionAdapterOptions options; @@ -41,6 +43,45 @@ public HttpsExtensionConnectionAdapter(HttpsConnectionAdapterOptions options) public Task OnConnectionAsync(ConnectionAdapterContext context) => Task.Run(() => this.InnerOnConnectionAsync(context)); + static void EnsureCertificateIsAllowedForServerAuth(X509Certificate2 certificate) + { + /* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1) + * If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages. + * + * See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/ + * + * From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage" + * + * If the (Extended Key Usage) extension is present, then the certificate MUST only be used + * for one of the purposes indicated. If multiple purposes are + * indicated the application need not recognize all purposes indicated, + * as long as the intended purpose is present. Certificate using + * applications MAY require that a particular purpose be indicated in + * order for the certificate to be acceptable to that application. + */ + + bool hasEkuExtension = false; + + foreach (X509EnhancedKeyUsageExtension extension in certificate.Extensions.OfType()) + { + hasEkuExtension = true; + foreach (Oid oid in extension.EnhancedKeyUsages) + { + if (oid.Value.Equals(ServerAuthenticationOid, StringComparison.Ordinal)) + { + return; + } + } + } + + if (hasEkuExtension) + { + throw new InvalidOperationException("InvalidServerCertificateEku"); + } + } + + static void ObserveTaskException(Task task) => _ = task.ContinueWith(t => _ = t.Exception, TaskScheduler.Current); + async Task InnerOnConnectionAsync(ConnectionAdapterContext context) { SslStream sslStream; @@ -161,69 +202,6 @@ await sslStream.AuthenticateAsServerAsync( return new HttpsAdaptedConnection(sslStream); } - static void EnsureCertificateIsAllowedForServerAuth(X509Certificate2 certificate) - { - /* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1) - * If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages. - * - * See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/ - * - * From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage" - * - * If the (Extended Key Usage) extension is present, then the certificate MUST only be used - * for one of the purposes indicated. If multiple purposes are - * indicated the application need not recognize all purposes indicated, - * as long as the intended purpose is present. Certificate using - * applications MAY require that a particular purpose be indicated in - * order for the certificate to be acceptable to that application. - */ - - bool hasEkuExtension = false; - - foreach (X509EnhancedKeyUsageExtension extension in certificate.Extensions.OfType()) - { - hasEkuExtension = true; - foreach (Oid oid in extension.EnhancedKeyUsages) - { - if (oid.Value.Equals(ServerAuthenticationOid, StringComparison.Ordinal)) - { - return; - } - } - } - - if (hasEkuExtension) - { - throw new InvalidOperationException("InvalidServerCertificateEku"); - } - } - - static void ObserveTaskException(Task task) => _ = task.ContinueWith(t => _ = t.Exception, TaskScheduler.Current); - - class HttpsAdaptedConnection : IAdaptedConnection - { - readonly SslStream sslStream; - - public HttpsAdaptedConnection(SslStream sslStream) - { - this.sslStream = sslStream; - } - - public Stream ConnectionStream => this.sslStream; - - public void Dispose() => this.sslStream.Dispose(); - } - - class ClosedAdaptedConnection : IAdaptedConnection - { - public Stream ConnectionStream { get; } = new ClosedStream(); - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", Justification = "Field does not need to be disposed.")] - public void Dispose() - { - } - } - internal class ClosedStream : Stream { static readonly Task ZeroResultTask = Task.FromResult(result: 0); @@ -260,10 +238,20 @@ public override void Flush() public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } + class ClosedAdaptedConnection : IAdaptedConnection + { + public Stream ConnectionStream { get; } = new ClosedStream(); + + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", Justification = "Field does not need to be disposed.")] + public void Dispose() + { + } + } + static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HttpEventIds.HttpsExtensionConnectionAdapter; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -281,5 +269,19 @@ public static void AuthenticationFailed() => public static void AuthenticationSuccess() => Log.LogDebug((int)EventIds.AuthenticationSuccess, "HttpExtensionConnectionAdapter authentication succeeded"); } + + class HttpsAdaptedConnection : IAdaptedConnection + { + readonly SslStream sslStream; + + public HttpsAdaptedConnection(SslStream sslStream) + { + this.sslStream = sslStream; + } + + public Stream ConnectionStream => this.sslStream; + + public void Dispose() => this.sslStream.Dispose(); + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/ExceptionFilter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/ExceptionFilter.cs index 5b11a1715c5..e746403c365 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/ExceptionFilter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/ExceptionFilter.cs @@ -1,12 +1,12 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Http { + using System; + using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; - using System; - using System.Net; public class ExceptionFilter : IExceptionFilter { @@ -34,8 +34,8 @@ public void OnException(ExceptionContext context) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HttpEventIds.ExceptionFilter; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -54,4 +54,4 @@ public static void UnknownException(ExceptionContext context) } } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Extensions/HttpContextExtensions.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Extensions/HttpContextExtensions.cs index 91edd322efe..6dfd3a0dbde 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Extensions/HttpContextExtensions.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Extensions/HttpContextExtensions.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Http.Extensions { - using Microsoft.AspNetCore.Http; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; + using Microsoft.AspNetCore.Http; public static class HttpContextExtensions { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpEventIds.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpEventIds.cs index b3a3b83de28..25bb2d66eda 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpEventIds.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpEventIds.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http { public class HttpEventIds { - const int EventIdStart = 6000; public const int AuthenticationMiddleware = EventIdStart; public const int ExceptionFilter = EventIdStart + 100; public const int TwinsController = EventIdStart + 200; @@ -11,5 +10,6 @@ public class HttpEventIds public const int WebSocketListenerRegistry = EventIdStart + 400; public const int WebSocketHandlingMiddleware = EventIdStart + 500; public const int HttpsExtensionConnectionAdapter = EventIdStart + 600; + const int EventIdStart = 6000; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpProtocolHead.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpProtocolHead.cs index 284961a6f5d..a464d9c24e7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpProtocolHead.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/HttpProtocolHead.cs @@ -37,8 +37,8 @@ public async Task CloseAsync(CancellationToken token) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HttpEventIds.HttpProtocolHead; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequest.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequest.cs index bd3f6d83f97..a2534930390 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequest.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequest.cs @@ -2,16 +2,16 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http { using System; + using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - using System.Text; public class MethodRequest { const int DeviceMethodDefaultResponseTimeoutInSeconds = 30; const int DeviceMethodDefaultConnectTimeoutInSeconds = 0; byte[] payloadBytes; - + public MethodRequest(string methodName, JRaw payload) : this(methodName, payload, DeviceMethodDefaultResponseTimeoutInSeconds, DeviceMethodDefaultConnectTimeoutInSeconds) { @@ -32,12 +32,6 @@ public MethodRequest(string methodName, JRaw payload, int? responseTimeoutInSeco [JsonProperty("payload")] public JRaw Payload { get; } - [JsonProperty("responseTimeoutInSeconds")] - internal int ResponseTimeoutInSeconds { get; } - - [JsonProperty("connectTimeoutInSeconds")] - internal int ConnectTimeoutInSeconds { get; } - [JsonIgnore] public TimeSpan ResponseTimeout => TimeSpan.FromSeconds(this.ResponseTimeoutInSeconds); @@ -58,5 +52,11 @@ public byte[] PayloadBytes return this.payloadBytes; } } + + [JsonProperty("responseTimeoutInSeconds")] + internal int ResponseTimeoutInSeconds { get; } + + [JsonProperty("connectTimeoutInSeconds")] + internal int ConnectTimeoutInSeconds { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequestValidator.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequestValidator.cs index f98125191ce..b38986b3d6a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequestValidator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/MethodRequestValidator.cs @@ -1,24 +1,24 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Http { + using System; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util; - using System; public class MethodRequestValidator : IValidator { + const int DeviceMethodNameMaxLength = 100; + const int PayloadMaxSizeBytes = 128 * 1024; // 128kb static readonly TimeSpan DeviceMethodMaxResponseTimeout = TimeSpan.FromMinutes(5); static readonly TimeSpan DeviceMethodMinResponseTimeout = TimeSpan.FromSeconds(5); static readonly TimeSpan DeviceMethodMaxDispatchTimeout = TimeSpan.FromMinutes(5); - const int DeviceMethodNameMaxLength = 100; - const int PayloadMaxSizeBytes = 128 * 1024; // 128kb public void Validate(MethodRequest methodRequest) { Preconditions.CheckNotNull(methodRequest, nameof(methodRequest)); Preconditions.CheckNonWhiteSpace(methodRequest.MethodName, "MethodName"); - + if (methodRequest.MethodName.Length > DeviceMethodNameMaxLength) { throw new ArgumentException($"MethodName cannot be longer than {DeviceMethodNameMaxLength}"); @@ -26,7 +26,7 @@ public void Validate(MethodRequest methodRequest) if (methodRequest.ConnectTimeout > DeviceMethodMaxDispatchTimeout || methodRequest.ConnectTimeout < TimeSpan.Zero) { - throw new ArgumentOutOfRangeException($"ConnectTimeout has to be between 0 and {DeviceMethodMaxDispatchTimeout.TotalSeconds} seconds."); + throw new ArgumentOutOfRangeException($"ConnectTimeout has to be between 0 and {DeviceMethodMaxDispatchTimeout.TotalSeconds} seconds."); } if (methodRequest.ResponseTimeout > DeviceMethodMaxResponseTimeout || methodRequest.ResponseTimeout < DeviceMethodMinResponseTimeout) @@ -36,7 +36,7 @@ public void Validate(MethodRequest methodRequest) if (methodRequest.PayloadBytes != null && methodRequest.PayloadBytes.Length > PayloadMaxSizeBytes) { - throw new ArgumentException($"Payload should not be greater than {PayloadMaxSizeBytes} bytes."); + throw new ArgumentException($"Payload should not be greater than {PayloadMaxSizeBytes} bytes."); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Microsoft.Azure.Devices.Edge.Hub.Http.csproj b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Microsoft.Azure.Devices.Edge.Hub.Http.csproj index f03ee500a23..68953212e3a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Microsoft.Azure.Devices.Edge.Hub.Http.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/Microsoft.Azure.Devices.Edge.Hub.Http.csproj @@ -36,4 +36,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/WebSocketListenerRegistry.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/WebSocketListenerRegistry.cs index 73c4a3887ed..3c8de160fdc 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/WebSocketListenerRegistry.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/WebSocketListenerRegistry.cs @@ -25,7 +25,7 @@ public Option GetListener(IEnumerable requestedProto return Option.Some(webSocketListener); } } - + return Option.None(); } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/controllers/TwinsController.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/controllers/TwinsController.cs index 9f50ef0b227..3c054d9fe29 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/controllers/TwinsController.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/controllers/TwinsController.cs @@ -19,7 +19,7 @@ public class TwinsController : Controller readonly Task edgeHubGetter; readonly IValidator validator; IIdentity identity; - + public TwinsController(Task edgeHub, IValidator validator) { this.edgeHubGetter = Preconditions.CheckNotNull(edgeHub, nameof(edgeHub)); @@ -32,6 +32,7 @@ public override void OnActionExecuting(ActionExecutingContext context) { this.identity = contextIdentity as IIdentity; } + base.OnActionExecuting(context); } @@ -58,20 +59,19 @@ public Task InvokeModuleMethodAsync([FromRoute] string deviceId, return this.InvokeMethodAsync(directMethodRequest); } - async Task InvokeMethodAsync(DirectMethodRequest directMethodRequest) - { - Events.ReceivedMethodCall(directMethodRequest, this.identity); - IEdgeHub edgeHub = await this.edgeHubGetter; - DirectMethodResponse directMethodResponse = await edgeHub.InvokeMethodAsync(this.identity.Id, directMethodRequest); - Events.ReceivedMethodCallResponse(directMethodRequest, this.identity); + internal static MethodResult GetMethodResult(DirectMethodResponse directMethodResponse) => + directMethodResponse.Exception.Map(e => new MethodErrorResult(directMethodResponse.Status, null, e.Message, string.Empty) as MethodResult) + .GetOrElse(() => new MethodResult(directMethodResponse.Status, GetRawJson(directMethodResponse.Data))); - MethodResult methodResult = GetMethodResult(directMethodResponse); - HttpResponse response = this.Request?.HttpContext?.Response; - if (response != null) + internal static JRaw GetRawJson(byte[] bytes) + { + if (bytes == null || bytes.Length == 0) { - response.ContentLength = GetContentLength(methodResult); + return null; } - return this.StatusCode((int)directMethodResponse.HttpStatusCode, methodResult); + + string json = Encoding.UTF8.GetString(bytes); + return new JRaw(json); } static int GetContentLength(MethodResult methodResult) @@ -80,25 +80,27 @@ static int GetContentLength(MethodResult methodResult) return json.Length; } - internal static MethodResult GetMethodResult(DirectMethodResponse directMethodResponse) => - directMethodResponse.Exception.Map(e => new MethodErrorResult(directMethodResponse.Status, null, e.Message, string.Empty) as MethodResult) - .GetOrElse(() => new MethodResult(directMethodResponse.Status, GetRawJson(directMethodResponse.Data))); - - internal static JRaw GetRawJson(byte[] bytes) + async Task InvokeMethodAsync(DirectMethodRequest directMethodRequest) { - if (bytes == null || bytes.Length == 0) + Events.ReceivedMethodCall(directMethodRequest, this.identity); + IEdgeHub edgeHub = await this.edgeHubGetter; + DirectMethodResponse directMethodResponse = await edgeHub.InvokeMethodAsync(this.identity.Id, directMethodRequest); + Events.ReceivedMethodCallResponse(directMethodRequest, this.identity); + + MethodResult methodResult = GetMethodResult(directMethodResponse); + HttpResponse response = this.Request?.HttpContext?.Response; + if (response != null) { - return null; + response.ContentLength = GetContentLength(methodResult); } - string json = Encoding.UTF8.GetString(bytes); - return new JRaw(json); + return this.StatusCode((int)directMethodResponse.HttpStatusCode, methodResult); } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HttpEventIds.TwinsController; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/AuthenticationMiddleware.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/AuthenticationMiddleware.cs index 89cb172bd68..7f8765e8db6 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/AuthenticationMiddleware.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/AuthenticationMiddleware.cs @@ -74,6 +74,7 @@ public async Task Invoke(HttpContext context) { return LogAndReturnFailure("Id header doesn't contain device Id and module Id as expected."); } + string deviceId = clientIdParts[0]; string moduleId = clientIdParts[1]; @@ -90,12 +91,12 @@ public async Task Invoke(HttpContext context) // Authorization header may be present in the QueryNameValuePairs as per Azure standards, // So check in the query parameters first. List authorizationQueryParameters = context.Request.Query - .Where(p => p.Key.Equals(HeaderNames.Authorization, StringComparison.OrdinalIgnoreCase)) - .SelectMany(p => p.Value) - .ToList(); + .Where(p => p.Key.Equals(HeaderNames.Authorization, StringComparison.OrdinalIgnoreCase)) + .SelectMany(p => p.Value) + .ToList(); if (!(context.Request.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorizationHeaderValues) - && authorizationQueryParameters.Count == 0)) + && authorizationQueryParameters.Count == 0)) { return LogAndReturnFailure("Authorization header missing"); } @@ -163,8 +164,8 @@ static async Task WriteErrorResponse(HttpContext context, string message) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HttpEventIds.AuthenticationMiddleware; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/WebSocketHandlingMiddleware.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/WebSocketHandlingMiddleware.cs index bceee3466fd..a4d110bad13 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/WebSocketHandlingMiddleware.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Http/middleware/WebSocketHandlingMiddleware.cs @@ -7,8 +7,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Middleware using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - using AspNetCore.Http; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Http.Extensions; using Microsoft.Azure.Devices.Edge.Util; @@ -68,6 +68,7 @@ async Task ProcessRequestAsync(HttpContext context, IWebSocketListener listener, { localEndPoint = Option.Some(new IPEndPoint(context.Connection.LocalIpAddress, context.Connection.LocalPort)); } + var remoteEndPoint = new IPEndPoint(context.Connection.RemoteIpAddress, context.Connection.RemotePort); X509Certificate2 cert = await context.Connection.GetClientCertificateAsync(); @@ -79,15 +80,15 @@ async Task ProcessRequestAsync(HttpContext context, IWebSocketListener listener, else { await listener.ProcessWebSocketRequestAsync(webSocket, localEndPoint, remoteEndPoint, correlationId); - } + } Events.WebSocketRequestCompleted(context.TraceIdentifier, correlationId); } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = HttpEventIds.WebSocketHandlingMiddleware; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ByteBufferConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ByteBufferConverter.cs index f2d1b530ae5..b7cf23d8d44 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ByteBufferConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ByteBufferConverter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { - using System.IO; using DotNetty.Buffers; using Microsoft.Azure.Devices.Edge.Util; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceIdentityProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceIdentityProvider.cs index 859e91eda71..016c585258c 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceIdentityProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceIdentityProvider.cs @@ -115,7 +115,6 @@ internal static (string deviceId, string moduleId, string deviceClientType) Pars // "iotHub1/device1/module1/foo?bar=b1&api-version=2010-01-01&DeviceClientType=customDeviceClient1" // "iotHub1/device1?&api-version=2010-01-01&DeviceClientType=customDeviceClient1" // "iotHub1/device1/module1?&api-version=2010-01-01&DeviceClientType=customDeviceClient1" - string deviceId; string moduleId = string.Empty; IDictionary queryParameters; @@ -129,9 +128,9 @@ internal static (string deviceId, string moduleId, string deviceClientType) Pars deviceId = usernameSegments[1].Trim(); queryParameters = ParseDeviceClientType(usernameSegments[2].Substring(1).Trim()); } - // edgeHubHostName/device1/module1/?apiVersion=10-2-3&DeviceClientType=foo else if (usernameSegments.Length == 4) { + // edgeHubHostName/device1/module1/?apiVersion=10-2-3&DeviceClientType=foo deviceId = usernameSegments[1].Trim(); moduleId = usernameSegments[2].Trim(); queryParameters = ParseDeviceClientType(usernameSegments[3].Substring(1).Trim()); @@ -149,19 +148,19 @@ internal static (string deviceId, string moduleId, string deviceClientType) Pars deviceId = usernameSegments[1].Trim(); queryParameters = ParseDeviceClientType(usernameSegments[2].Trim()); } - // edgeHubHostName/device1/module1/apiVersion=10-2-3&DeviceClientType=foo else if (usernameSegments.Length == 4 && usernameSegments[3].Contains("api-version=")) { + // edgeHubHostName/device1/module1/apiVersion=10-2-3&DeviceClientType=foo deviceId = usernameSegments[1].Trim(); moduleId = usernameSegments[2].Trim(); queryParameters = ParseDeviceClientType(usernameSegments[3].Trim()); } - // The Azure ML container is using an older client that returns a device client with the following format - - // username = edgeHubHostName/deviceId/moduleId/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client/1.5.1-preview-003 - // Notice how the DeviceClientType parameter is separated by a '/' instead of a '&', giving a usernameSegments.Length of 6 instead of the expected 4 - // To allow those clients to work, check for that specific api-version, and version. else if (usernameSegments.Length == 6 && username.EndsWith("/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client/1.5.1-preview-003", StringComparison.OrdinalIgnoreCase)) { + // The Azure ML container is using an older client that returns a device client with the following format - + // username = edgeHubHostName/deviceId/moduleId/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client/1.5.1-preview-003 + // Notice how the DeviceClientType parameter is separated by a '/' instead of a '&', giving a usernameSegments.Length of 6 instead of the expected 4 + // To allow those clients to work, check for that specific api-version, and version. deviceId = usernameSegments[1].Trim(); moduleId = usernameSegments[2].Trim(); queryParameters = new Dictionary @@ -198,7 +197,6 @@ internal static (string deviceId, string moduleId, string deviceClientType) Pars static IDictionary ParseDeviceClientType(string queryParameterString) { // example input: "api-version=version&DeviceClientType=url-escaped-string&other-prop=value&some-other-prop" - var kvsep = new[] { '=' }; Dictionary queryParameters = queryParameterString @@ -214,8 +212,8 @@ static IDictionary ParseDeviceClientType(string queryParameterSt static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.SasTokenDeviceIdentityProvider; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -226,9 +224,6 @@ enum EventIds ErrorCreatingIdentity } - static string GetId(string deviceId, string moduleId) => - string.IsNullOrWhiteSpace(moduleId) ? deviceId : $"{deviceId}/{moduleId}"; - public static void Success(string clientId, string username) => Log.LogInformation((int)EventIds.CreateSuccess, Invariant($"Successfully generated identity for clientId {clientId} and username {username}")); @@ -243,6 +238,9 @@ public static void AuthNotFound(string deviceId, string moduleId) public static void ErrorCreatingIdentity(Exception ex) => Log.LogError((int)EventIds.ErrorCreatingIdentity, ex, "Error creating client identity"); + + static string GetId(string deviceId, string moduleId) => + string.IsNullOrWhiteSpace(moduleId) ? deviceId : $"{deviceId}/{moduleId}"; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceProxy.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceProxy.cs index 02c2d9a9856..884ec7157e4 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceProxy.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/DeviceProxy.cs @@ -12,8 +12,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt using Microsoft.Azure.Devices.ProtocolGateway.Mqtt; using Microsoft.Extensions.Logging; using static System.FormattableString; - using IMessage = Core.IMessage; - using IProtocolGatewayMessage = ProtocolGateway.Messaging.IMessage; + using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; + using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; public class DeviceProxy : IDeviceProxy { @@ -22,8 +22,11 @@ public class DeviceProxy : IDeviceProxy readonly AtomicBoolean isActive; readonly IByteBufferConverter byteBufferConverter; - public DeviceProxy(IMessagingChannel channel, IIdentity identity, - IMessageConverter messageConverter, IByteBufferConverter byteBufferConverter) + public DeviceProxy( + IMessagingChannel channel, + IIdentity identity, + IMessageConverter messageConverter, + IByteBufferConverter byteBufferConverter) { this.isActive = new AtomicBoolean(true); this.channel = Preconditions.CheckNotNull(channel, nameof(channel)); @@ -34,6 +37,8 @@ public DeviceProxy(IMessagingChannel channel, IIdentity public IIdentity Identity { get; } + public bool IsActive => this.isActive.Get(); + public Task CloseAsync(Exception ex) { if (this.isActive.GetAndSet(false)) @@ -41,6 +46,7 @@ public Task CloseAsync(Exception ex) this.channel.Close(ex ?? new EdgeHubConnectionException($"Connection closed for device {this.Identity.Id}.")); Events.Close(this.Identity); } + return TaskEx.Done; } @@ -74,6 +80,7 @@ public Task SendMessageAsync(IMessage message, string input) this.channel.Handle(pgMessage); result = true; } + return Task.FromResult(result); } @@ -101,20 +108,18 @@ public Task SendTwinUpdate(IMessage twin) public Task OnDesiredPropertyUpdates(IMessage desiredProperties) { desiredProperties.SystemProperties[SystemProperties.OutboundUri] = - Constants.OutboundUriTwinDesiredPropertyUpdate; + Constants.OutboundUriTwinDesiredPropertyUpdate; this.channel.Handle(this.messageConverter.FromMessage(desiredProperties)); Events.SentDesiredPropertyUpdate(this.Identity); return Task.FromResult(true); } - public bool IsActive => this.isActive.Get(); - public Task> GetUpdatedIdentity() => Task.FromResult(Option.None()); static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.DeviceProxy; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConversionConfiguration.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConversionConfiguration.cs index 16230afa3f1..647219a9c86 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConversionConfiguration.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConversionConfiguration.cs @@ -6,11 +6,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt public class MessageAddressConversionConfiguration { - public IList InboundTemplates { get; } - public IDictionary OutboundTemplates { get; } - - public MessageAddressConversionConfiguration() : - this(new List(), new Dictionary()) + public MessageAddressConversionConfiguration() + : this(new List(), new Dictionary()) { } @@ -22,5 +19,9 @@ public MessageAddressConversionConfiguration(IList inboundTemplates, IDi this.InboundTemplates = inboundTemplates; this.OutboundTemplates = outboundTemplates; } + + public IList InboundTemplates { get; } + + public IDictionary OutboundTemplates { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConverter.cs index 4aa9b38ada8..eb0a865170a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessageAddressConverter.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { + using System; + using System.Collections.Generic; + using System.Linq; using Microsoft.Azure.Devices.Client.Common; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.ProtocolGateway; using Microsoft.Extensions.Logging; - using System; - using System.Collections.Generic; - using System.Linq; using static System.FormattableString; using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; @@ -20,7 +20,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt /// MQTT protocol head however we transform these names into MQTT topic names. This /// class, in conjunction with the /// class, handles this task. - /// + /// /// See documentation for and /// for additional detail on how /// this transformation works. @@ -62,7 +62,7 @@ public bool TryBuildProtocolAddressFromEdgeHubMessage(string endPointUri, IMessa { try { - address = template.Bind(message.SystemProperties); + address = template.Bind(message.SystemProperties); if (!string.IsNullOrWhiteSpace(address) && messagePropertiesToSend != null && messagePropertiesToSend.Count > 0) { address = Invariant($"{address.TrimEnd('/')}/{UrlEncodedDictionarySerializer.Serialize(messagePropertiesToSend)}"); @@ -109,8 +109,8 @@ public bool TryParseProtocolMessagePropsFromAddress(IProtocolGatewayMessage mess foreach (KeyValuePair match in matches[0]) { - // If the template has a key called "params" then it contains all the properties set by the user on - // the sent message in query string format. So get the value and parse it. + // If the template has a key called "params" then it contains all the properties set by the user on + // the sent message in query string format. So get the value and parse it. if (match.Key == "params") { if (!string.IsNullOrWhiteSpace(match.Value)) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessagingServiceClient.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessagingServiceClient.cs index 4b734d6587a..1b43d323f82 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessagingServiceClient.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MessagingServiceClient.cs @@ -13,7 +13,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using static System.FormattableString; - using IProtocolGatewayMessage = ProtocolGateway.Messaging.IMessage; + using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; + using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; public class MessagingServiceClient : IMessagingServiceClient { @@ -30,6 +31,8 @@ public MessagingServiceClient(IDeviceListener deviceListener, IMessageConverter< this.byteBufferConverter = Preconditions.CheckNotNull(byteBufferConverter, nameof(byteBufferConverter)); } + public int MaxPendingMessages => 100; + public IProtocolGatewayMessage CreateMessage(string address, IByteBuffer payload) { ProtocolGatewayMessage message = new ProtocolGatewayMessage.Builder(payload, address) @@ -44,11 +47,6 @@ public void BindMessagingChannel(IMessagingChannel chan Events.BindMessageChannel(this.deviceListener.Identity); } - // We are only interested in non-NULL message IDs which are different than TwinLockToken. A twin - // message sent out via PG for example will cause a feedback to be generated - // with TwinLockToken as message ID which is redundant. - static bool IsValidMessageId(string messageId) => messageId != null && messageId != Constants.TwinLockToken; - public Task AbandonAsync(string messageId) => IsValidMessageId(messageId) ? this.deviceListener.ProcessMessageFeedbackAsync(messageId, FeedbackStatus.Abandon) : Task.CompletedTask; @@ -89,7 +87,22 @@ public async Task SendAsync(IProtocolGatewayMessage message) } } - public int MaxPendingMessages => 100; + // We are only interested in non-NULL message IDs which are different than TwinLockToken. A twin + // message sent out via PG for example will cause a feedback to be generated + // with TwinLockToken as message ID which is redundant. + static bool IsValidMessageId(string messageId) => messageId != null && messageId != Constants.TwinLockToken; + + static bool IsTwinAddress(string topicName) => topicName.StartsWith(Constants.TwinPrefix, StringComparison.Ordinal); + + static bool IsMethodResponseAddress(string topicName) => topicName.StartsWith(Constants.MethodPrefix, StringComparison.Ordinal); + + static void EnsureNoSubresource(StringSegment subresource) + { + if (subresource.Length != 0) + { + throw new InvalidOperationException($"Further resource specialization is not supported: `{subresource.ToString()}`."); + } + } async Task ProcessTwinAsync(IProtocolGatewayMessage protocolGatewayMessage) { @@ -115,7 +128,7 @@ async Task ProcessTwinAsync(IProtocolGatewayMessage protocolGatewayMessage) case TwinAddressHelper.Operation.TwinPatchReportedState: EnsureNoSubresource(subresource); - Core.IMessage forwardMessage = new EdgeMessage.Builder(this.byteBufferConverter.ToByteArray(protocolGatewayMessage.Payload)) + IMessage forwardMessage = new EdgeMessage.Builder(this.byteBufferConverter.ToByteArray(protocolGatewayMessage.Payload)) .Build(); await this.deviceListener.UpdateReportedPropertiesAsync(forwardMessage, hasCorrelationId ? correlationId.ToString() : string.Empty); Events.UpdateReportedProperties(this.deviceListener.Identity); @@ -135,7 +148,7 @@ Task ProcessMethodResponse(IProtocolGatewayMessage message) { try { - Core.IMessage coreMessage = this.messageConverter.ToMessage(message); + IMessage coreMessage = this.messageConverter.ToMessage(message); this.deviceListener.ProcessMethodResponseAsync(coreMessage); return Task.CompletedTask; } @@ -150,7 +163,7 @@ Task ProcessMessageAsync(IProtocolGatewayMessage message) { try { - Core.IMessage coreMessage = this.messageConverter.ToMessage(message); + IMessage coreMessage = this.messageConverter.ToMessage(message); Events.ProcessMessage(this.deviceListener.Identity); return this.deviceListener.ProcessDeviceMessageAsync(coreMessage); } @@ -161,22 +174,10 @@ Task ProcessMessageAsync(IProtocolGatewayMessage message) } } - static bool IsTwinAddress(string topicName) => topicName.StartsWith(Constants.TwinPrefix, StringComparison.Ordinal); - - static bool IsMethodResponseAddress(string topicName) => topicName.StartsWith(Constants.MethodPrefix, StringComparison.Ordinal); - - static void EnsureNoSubresource(StringSegment subresource) - { - if (subresource.Length != 0) - { - throw new InvalidOperationException($"Further resource specialization is not supported: `{subresource.ToString()}`."); - } - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.MessagingServiceClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/Microsoft.Azure.Devices.Edge.Hub.Mqtt.csproj b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/Microsoft.Azure.Devices.Edge.Hub.Mqtt.csproj index aaab5780a38..3bb18b5862e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/Microsoft.Azure.Devices.Edge.Hub.Mqtt.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/Microsoft.Azure.Devices.Edge.Hub.Mqtt.csproj @@ -30,4 +30,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttConnectionProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttConnectionProvider.cs index 436450b5142..0b6344902c3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttConnectionProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttConnectionProvider.cs @@ -37,6 +37,8 @@ public async Task Connect(IDeviceIdentity deviceidentity) return messagingBridge; } + public void Dispose() => this.Dispose(true); + protected virtual void Dispose(bool disposing) { if (disposing) @@ -44,7 +46,5 @@ protected virtual void Dispose(bool disposing) this.connectionProvider?.Dispose(); } } - - public void Dispose() => this.Dispose(true); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttEventIds.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttEventIds.cs index 6f16cc9d8fd..953f71ba944 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttEventIds.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttEventIds.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { public static class MqttEventIds { - const int EventIdStart = 4000; public const int SasTokenDeviceIdentityProvider = EventIdStart; public const int DeviceProxy = EventIdStart + 100; public const int MessagingServiceClient = EventIdStart + 200; @@ -11,5 +10,6 @@ public static class MqttEventIds public const int SessionStateStoragePersistenceProvider = EventIdStart + 400; public const int MqttWebSocketListener = EventIdStart + 500; public const int ServerWebSocketChannel = EventIdStart + 600; + const int EventIdStart = 4000; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttFeedbackMessage.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttFeedbackMessage.cs index 12dfad20200..020c3395ce4 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttFeedbackMessage.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttFeedbackMessage.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt class MqttFeedbackMessage : IFeedbackMessage { readonly IMessage message; + public MqttFeedbackMessage(IMessage message, FeedbackStatus status) { this.message = message; @@ -23,4 +24,4 @@ public MqttFeedbackMessage(IMessage message, FeedbackStatus status) public void Dispose() => this.message.Dispose(); } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttProtocolHead.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttProtocolHead.cs index 21f0bb05607..fd19ee511af 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttProtocolHead.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttProtocolHead.cs @@ -26,6 +26,13 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt public class MqttProtocolHead : IProtocolHead { + const int MqttsPort = 8883; + const int DefaultListenBacklogSize = 200; // connections allowed pending accept + const int DefaultParentEventLoopCount = 1; + const int DefaultMaxInboundMessageSize = 256 * 1024; + const int DefaultThreadCount = 200; + const bool AutoRead = false; + readonly ILogger logger = Logger.Factory.CreateLogger(); readonly ISettingsProvider settingsProvider; readonly X509Certificate tlsCertificate; @@ -37,12 +44,6 @@ public class MqttProtocolHead : IProtocolHead readonly IByteBufferAllocator byteBufferAllocator; readonly bool clientCertAuthAllowed; - const int MqttsPort = 8883; - const int DefaultListenBacklogSize = 200; // connections allowed pending accept - const int DefaultParentEventLoopCount = 1; - const int DefaultMaxInboundMessageSize = 256 * 1024; - const int DefaultThreadCount = 200; - const bool AutoRead = false; IChannel serverChannel; IEventLoopGroup eventLoopGroup; @@ -101,7 +102,6 @@ public async Task CloseAsync(CancellationToken token) await (this.eventLoopGroup?.ShutdownGracefullyAsync() ?? TaskEx.Done); // TODO: gracefully shutdown the MultithreadEventLoopGroup in MqttWebSocketListener? // TODO: this.webSocketListenerRegistry.TryUnregister("mqtts")? - this.logger.LogInformation("Stopped"); } catch (Exception ex) @@ -150,8 +150,7 @@ ServerBootstrap SetupServerBootstrap() // configure the channel pipeline of the new Channel by adding handlers TlsSettings serverSettings = new ServerTlsSettings( certificate: this.tlsCertificate, - negotiateClientCertificate: this.clientCertAuthAllowed - ); + negotiateClientCertificate: this.clientCertAuthAllowed); channel.Pipeline.AddLast( new TlsHandler( diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttSettingsProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttSettingsProvider.cs index 34b26200191..91a83691d79 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttSettingsProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttSettingsProvider.cs @@ -10,8 +10,8 @@ public class MqttSettingsProvider : ISettingsProvider readonly IConfiguration mqttSettings; public MqttSettingsProvider(IConfiguration mqttSettings) - { - this.mqttSettings = Preconditions.CheckNotNull(mqttSettings, nameof(mqttSettings)); + { + this.mqttSettings = Preconditions.CheckNotNull(mqttSettings, nameof(mqttSettings)); } public bool TryGetSetting(string name, out string value) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttWebSocketListener.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttWebSocketListener.cs index 214903fd198..42270ef67f8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttWebSocketListener.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/MqttWebSocketListener.cs @@ -106,7 +106,7 @@ public async Task ProcessWebSocketRequestAsyncInternal( Events.Established(correlationId); - await serverChannel.WebSocketClosed.Task; // This will wait until the websocket is closed + await serverChannel.WebSocketClosed.Task; // This will wait until the websocket is closed } catch (Exception ex) when (!ex.IsFatal()) { @@ -117,8 +117,8 @@ public async Task ProcessWebSocketRequestAsyncInternal( static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.MqttWebSocketListener; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayIdentity.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayIdentity.cs index 3cab5e70dcb..d9f8c629eef 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayIdentity.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayIdentity.cs @@ -3,8 +3,9 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util; + using Microsoft.Azure.Devices.ProtocolGateway.Identity; - class ProtocolGatewayIdentity : ProtocolGateway.Identity.IDeviceIdentity + class ProtocolGatewayIdentity : IDeviceIdentity { public ProtocolGatewayIdentity(IClientCredentials clientCredentials) { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessage.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessage.cs index 372527b6196..b72259a3ae3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessage.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessage.cs @@ -45,25 +45,6 @@ public class ProtocolGatewayMessage : IMessage public ulong SequenceNumber { get; } - #region IDisposable Support - - protected virtual void Dispose(bool disposing) - { - if (!this.isDisposed.GetAndSet(true)) - { - if (disposing) - { - this.Payload?.SafeRelease(); - } - } - } - - public void Dispose() - { - this.Dispose(true); - } - #endregion - public class Builder { readonly IByteBuffer payload; @@ -122,5 +103,25 @@ public ProtocolGatewayMessage Build() return new ProtocolGatewayMessage(this.payload, this.address, this.properties, this.id, this.createdTimeUtc, this.deliveryCount, this.sequenceNumber); } } + + #region IDisposable Support + + protected virtual void Dispose(bool disposing) + { + if (!this.isDisposed.GetAndSet(true)) + { + if (disposing) + { + this.Payload?.SafeRelease(); + } + } + } + + public void Dispose() + { + this.Dispose(true); + } + + #endregion } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessageConverter.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessageConverter.cs index b00ddab2502..fb4fce60404 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessageConverter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ProtocolGatewayMessageConverter.cs @@ -3,6 +3,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { using System; using System.Collections.Generic; + using System.Globalization; using DotNetty.Buffers; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util; @@ -10,7 +11,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; public class ProtocolGatewayMessageConverter : IMessageConverter - { + { readonly MessageAddressConverter addressConvertor; readonly IByteBufferConverter byteBufferConverter; @@ -22,7 +23,7 @@ public ProtocolGatewayMessageConverter(MessageAddressConverter addressConvertor, public IMessage ToMessage(IProtocolGatewayMessage sourceMessage) { - if(!this.addressConvertor.TryParseProtocolMessagePropsFromAddress(sourceMessage)) + if (!this.addressConvertor.TryParseProtocolMessagePropsFromAddress(sourceMessage)) { throw new InvalidOperationException("Topic name could not be matched against any of the configured routes."); } @@ -30,7 +31,6 @@ public IMessage ToMessage(IProtocolGatewayMessage sourceMessage) byte[] payloadBytes = this.byteBufferConverter.ToByteArray(sourceMessage.Payload); // TODO - What about the other properties (like sequence number, etc)? Ignoring for now, as they are not used anyways. - var systemProperties = new Dictionary(); var properties = new Dictionary(); if (sourceMessage.Properties.TryGetValue(TemplateParameters.DeviceIdTemplateParam, out string deviceIdValue)) @@ -71,7 +71,7 @@ public IProtocolGatewayMessage FromMessage(IMessage message) DateTime createdTimeUtc = DateTime.UtcNow; if (message.SystemProperties.TryGetValue(SystemProperties.EnqueuedTime, out string createdTime)) { - createdTimeUtc = DateTime.Parse(createdTime, null, System.Globalization.DateTimeStyles.RoundtripKind); + createdTimeUtc = DateTime.Parse(createdTime, null, DateTimeStyles.RoundtripKind); } if (!message.SystemProperties.TryGetValue(SystemProperties.OutboundUri, out string uriTemplateKey)) @@ -85,7 +85,7 @@ public IProtocolGatewayMessage FromMessage(IMessage message) properties.Add(property); } - foreach(KeyValuePair systemProperty in message.SystemProperties) + foreach (KeyValuePair systemProperty in message.SystemProperties) { if (SystemProperties.OutgoingSystemPropertiesMap.TryGetValue(systemProperty.Key, out string onWirePropertyName)) { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannel.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannel.cs index da2efa96901..c0e88bfcff9 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannel.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannel.cs @@ -22,10 +22,6 @@ public sealed class ServerWebSocketChannel : AbstractChannel, IDisposable volatile bool active; - internal bool ReadPending { get; set; } - - internal bool WriteInProgress { get; set; } - public ServerWebSocketChannel(WebSocket webSocket, EndPoint remoteEndPoint) : base(null) { @@ -49,6 +45,14 @@ public ServerWebSocketChannel(WebSocket webSocket, EndPoint remoteEndPoint) public TaskCompletionSource WebSocketClosed => this.webSocketClosed; + protected override EndPoint LocalAddressInternal => throw new NotSupportedException(); + + protected override EndPoint RemoteAddressInternal { get; } + + internal bool ReadPending { get; set; } + + internal bool WriteInProgress { get; set; } + public ServerWebSocketChannel Option(ChannelOption option, T value) { Preconditions.CheckNotNull(option); @@ -57,38 +61,13 @@ public ServerWebSocketChannel Option(ChannelOption option, T value) return this; } - protected override EndPoint LocalAddressInternal => throw new NotSupportedException(); - - protected override EndPoint RemoteAddressInternal { get; } - - protected override IChannelUnsafe NewUnsafe() => new WebSocketChannelUnsafe(this); - - class WebSocketChannelUnsafe : AbstractUnsafe + public void Dispose() { - public WebSocketChannelUnsafe(AbstractChannel channel) - :base(channel) - { - } - - public override Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress) - { - throw new NotSupportedException("ServerWebSocketChannel does not support BindAsync()"); - } - - protected override void Flush0() - { - // Flush immediately only when there's no pending flush. - // If there's a pending flush operation, event loop will call FinishWrite() later, - // and thus there's no need to call it now. - if (((ServerWebSocketChannel)this.channel).WriteInProgress) - { - return; - } - - base.Flush0(); - } + this.writeCancellationTokenSource.Dispose(); } + protected override IChannelUnsafe NewUnsafe() => new WebSocketChannelUnsafe(this); + protected override bool IsCompatible(IEventLoop eventLoop) => true; protected override void DoBind(EndPoint localAddress) @@ -303,15 +282,10 @@ void Abort() } } - public void Dispose() - { - this.writeCancellationTokenSource.Dispose(); - } - static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.ServerWebSocketChannel; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { @@ -325,32 +299,58 @@ enum EventIds public static void TransportClosed(string correlationId) { - Log.LogInformation((int)Events.EventIds.TransportClosed, Invariant($"Transport closed. CorrelationId {correlationId}")); + Log.LogInformation((int)EventIds.TransportClosed, Invariant($"Transport closed. CorrelationId {correlationId}")); } public static void CloseException(string correlationId, Exception ex) { - Log.LogWarning((int)Events.EventIds.CloseException, ex, Invariant($"Error closing transport. CorrelationId {correlationId}")); + Log.LogWarning((int)EventIds.CloseException, ex, Invariant($"Error closing transport. CorrelationId {correlationId}")); } public static void ReadException(string correlationId, Exception ex) { - Log.LogWarning((int)Events.EventIds.ReadException, ex, Invariant($"Error reading transport. CorrelationId {correlationId}")); + Log.LogWarning((int)EventIds.ReadException, ex, Invariant($"Error reading transport. CorrelationId {correlationId}")); } public static void WriteException(string correlationId, Exception ex) { - Log.LogWarning((int)Events.EventIds.WriteException, ex, Invariant($"Error writing transport. CorrelationId {correlationId}")); + Log.LogWarning((int)EventIds.WriteException, ex, Invariant($"Error writing transport. CorrelationId {correlationId}")); } public static void TransportAborted(string correlationId) { - Log.LogInformation((int)Events.EventIds.TransportAborted, Invariant($"Transport aborted. CorrelationId {correlationId}")); + Log.LogInformation((int)EventIds.TransportAborted, Invariant($"Transport aborted. CorrelationId {correlationId}")); } public static void TransportAbortFailedException(string correlationId, Exception ex) { - Log.LogWarning((int)Events.EventIds.TransportAbortFailedException, ex, Invariant($"Error aborting transport. CorrelationId {correlationId}")); + Log.LogWarning((int)EventIds.TransportAbortFailedException, ex, Invariant($"Error aborting transport. CorrelationId {correlationId}")); + } + } + + class WebSocketChannelUnsafe : AbstractUnsafe + { + public WebSocketChannelUnsafe(AbstractChannel channel) + : base(channel) + { + } + + public override Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress) + { + throw new NotSupportedException("ServerWebSocketChannel does not support BindAsync()"); + } + + protected override void Flush0() + { + // Flush immediately only when there's no pending flush. + // If there's a pending flush operation, event loop will call FinishWrite() later, + // and thus there's no need to call it now. + if (((ServerWebSocketChannel)this.channel).WriteInProgress) + { + return; + } + + base.Flush0(); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannelConfig.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannelConfig.cs index b89967fde26..ad6f5bf5c69 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannelConfig.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/ServerWebSocketChannelConfig.cs @@ -8,6 +8,22 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt public class ServerWebSocketChannelConfig : IChannelConfiguration { + public TimeSpan ConnectTimeout { get; set; } + + public int WriteSpinCount { get; set; } + + public IByteBufferAllocator Allocator { get; set; } + + public IRecvByteBufAllocator RecvByteBufAllocator { get; set; } + + public bool AutoRead { get; set; } + + public int WriteBufferHighWaterMark { get; set; } + + public int WriteBufferLowWaterMark { get; set; } + + public IMessageSizeEstimator MessageSizeEstimator { get; set; } + public T GetOption(ChannelOption option) { Preconditions.CheckNotNull(option); @@ -16,34 +32,42 @@ public T GetOption(ChannelOption option) { return (T)(object)this.ConnectTimeout; // no boxing will happen, compiler optimizes away such casts } + if (ChannelOption.WriteSpinCount.Equals(option)) { return (T)(object)this.WriteSpinCount; } + if (ChannelOption.Allocator.Equals(option)) { return (T)this.Allocator; } + if (ChannelOption.RcvbufAllocator.Equals(option)) { return (T)this.RecvByteBufAllocator; } + if (ChannelOption.AutoRead.Equals(option)) { return (T)(object)this.AutoRead; } + if (ChannelOption.WriteBufferHighWaterMark.Equals(option)) { return (T)(object)this.WriteBufferHighWaterMark; } + if (ChannelOption.WriteBufferLowWaterMark.Equals(option)) { return (T)(object)this.WriteBufferLowWaterMark; } + if (ChannelOption.MessageSizeEstimator.Equals(option)) { return (T)this.MessageSizeEstimator; } + return default(T); } @@ -51,8 +75,6 @@ public T GetOption(ChannelOption option) public bool SetOption(ChannelOption option, T value) { - // this.Validate(option, value); - if (ChannelOption.ConnectTimeout.Equals(option)) { this.ConnectTimeout = (TimeSpan)(object)value; @@ -92,21 +114,5 @@ public bool SetOption(ChannelOption option, T value) return true; } - - public TimeSpan ConnectTimeout { get; set; } - - public int WriteSpinCount { get; set; } - - public IByteBufferAllocator Allocator { get; set; } - - public IRecvByteBufAllocator RecvByteBufAllocator { get; set; } - - public bool AutoRead { get; set; } - - public int WriteBufferHighWaterMark { get; set; } - - public int WriteBufferLowWaterMark { get; set; } - - public IMessageSizeEstimator MessageSizeEstimator { get; set; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionState.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionState.cs index b7c894fc163..4e02a7bccdf 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionState.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionState.cs @@ -15,8 +15,8 @@ public class SessionState : ISessionState readonly ReaderWriterLockSlim updateLock = new ReaderWriterLockSlim(); // set transient to false to get calls from Protocol Gateway when there are changes to the subscription - // because IsTransient is always set to false, this property is used to keep if the session was transient - // and it should not be saved to store in that case + // because IsTransient is always set to false, this property is used to keep if the session was transient + // and it should not be saved to store in that case public SessionState(bool transient) : this(false, !transient, new List(), new Dictionary()) { @@ -75,8 +75,6 @@ public ISessionState Copy() } } - internal void ClearRegistrations() => this.subscriptionRegistrations.Clear(); - public bool RemoveSubscription(string topicFilter) { this.updateLock.EnterWriteLock(); @@ -89,6 +87,7 @@ public bool RemoveSubscription(string topicFilter) this.subscriptions.RemoveAt(index); return true; } + return false; } finally @@ -120,6 +119,8 @@ public void AddOrUpdateSubscription(string topicFilter, QualityOfService qos) } } + internal void ClearRegistrations() => this.subscriptionRegistrations.Clear(); + int FindSubscriptionIndex(string topicFilter) { for (int i = this.subscriptions.Count - 1; i >= 0; i--) @@ -130,6 +131,7 @@ int FindSubscriptionIndex(string topicFilter) return i; } } + return -1; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStatePersistenceProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStatePersistenceProvider.cs index f21c25d1f50..75e072dcdc9 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStatePersistenceProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStatePersistenceProvider.cs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { - using Microsoft.Azure.Devices.Edge.Hub.Core; - using Microsoft.Azure.Devices.Edge.Hub.Core.Device; - using Microsoft.Azure.Devices.Edge.Util; - using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; - using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Hub.Core; + using Microsoft.Azure.Devices.Edge.Hub.Core.Device; + using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Concurrency; + using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; + using Microsoft.Extensions.Logging; using static System.FormattableString; using IDeviceIdentity = Microsoft.Azure.Devices.ProtocolGateway.Identity.IDeviceIdentity; @@ -39,9 +39,9 @@ public virtual Task GetAsync(IDeviceIdentity identity) } public virtual Task SetAsync(IDeviceIdentity identity, ISessionState sessionState) => - sessionState is SessionState registrationSessionState ? - this.ProcessSessionSubscriptions(identity.Id, registrationSessionState) : - Task.CompletedTask; + sessionState is SessionState registrationSessionState ? this.ProcessSessionSubscriptions(identity.Id, registrationSessionState) : Task.CompletedTask; + + public virtual Task DeleteAsync(IDeviceIdentity identity, ISessionState sessionState) => Task.CompletedTask; protected async Task ProcessSessionSubscriptions(string id, SessionState sessionState) { @@ -78,12 +78,11 @@ protected async Task ProcessSessionSubscriptions(string id, SessionState session } } } + // Don't clear subscriptions here. That way the subscriptions are set every time the connection - // is re-established. Setting subscriptions is an idempotent operation. + // is re-established. Setting subscriptions is an idempotent operation. } - public virtual Task DeleteAsync(IDeviceIdentity identity, ISessionState sessionState) => Task.CompletedTask; - internal static DeviceSubscription GetDeviceSubscription(string topicName) { Preconditions.CheckNonWhiteSpace(topicName, nameof(topicName)); @@ -115,8 +114,8 @@ internal static DeviceSubscription GetDeviceSubscription(string topicName) static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.SessionStatePersistenceProvider; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStateStoragePersistenceProvider.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStateStoragePersistenceProvider.cs index 3a26117cd6a..03beb4e058b 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStateStoragePersistenceProvider.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/SessionStateStoragePersistenceProvider.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt { + using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; + using Microsoft.Azure.Devices.Edge.Util.Concurrency; using Microsoft.Azure.Devices.ProtocolGateway.Identity; using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; using Microsoft.Extensions.Logging; - using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util.Concurrency; using static System.FormattableString; public class SessionStateStoragePersistenceProvider : SessionStatePersistenceProvider @@ -34,7 +34,7 @@ public override async Task SetAsync(IDeviceIdentity identity, ISessionState sess } } - public override Task DeleteAsync(IDeviceIdentity identity, ISessionState sessionState) => this.sessionStore.Remove(identity.Id); + public override Task DeleteAsync(IDeviceIdentity identity, ISessionState sessionState) => this.sessionStore.Remove(identity.Id); Task PersistToStore(string id, ISessionState sessionState) { @@ -46,13 +46,12 @@ Task PersistToStore(string id, ISessionState sessionState) return registrationSessionState.ShouldSaveToStore ? this.sessionStore.Put(id, registrationSessionState) : Task.CompletedTask; - } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = MqttEventIds.SessionStateStoragePersistenceProvider; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/TwinAddressHelper.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/TwinAddressHelper.cs index 12c5f6865c4..0b09b563126 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/TwinAddressHelper.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Mqtt/TwinAddressHelper.cs @@ -35,23 +35,27 @@ public static class TwinAddressHelper static readonly StringSegment EmptyStringSegment = new StringSegment(string.Empty); - // `end` is the index of the last character in the range, inclusive - static StringSegment StringSegmentRange(string buffer, int start, int end) => new StringSegment(buffer, start, end + 1 - start); - - static StringSegment StringSegmentAtOffset(string buffer, int offset) => StringSegmentRange(buffer, offset, buffer.Length - 1); + public enum Operation + { + Unknown, + InvalidTwinRequest, + TwinGetState, + TwinPatchReportedState, + DirectMethodResponse + } public static bool CheckTwinAddress(string topicName) => topicName.StartsWith(ServicePrefix, StringComparison.Ordinal); public static string FormatNotificationAddress(string version) => TwinPrefix + PatchMethod + SegmentSeparator + TwinNames.Properties + SegmentSeparator + TwinNames.Desired + SegmentSeparator - + PropertiesSegmentPrefix + TwinNames.Version + PropertyValueSeparator + version; + + PropertiesSegmentPrefix + TwinNames.Version + PropertyValueSeparator + version; public static string FormatDeviceMethodRequestAddress(string correlationId, string methodName) => DirectMethodPrefix + PostMethod + SegmentSeparator + methodName + SegmentSeparator + PropertiesSegmentPrefix + TwinNames.RequestId + PropertyValueSeparator + correlationId; public static string FormatTwinResponseAddress(string statusCode, string correlationId) => TwinPrefix + ResponseSegment + statusCode + SegmentSeparator + PropertiesSegmentPrefix - + TwinNames.RequestId + PropertyValueSeparator + correlationId; + + TwinNames.RequestId + PropertyValueSeparator + correlationId; public static string FormatTwinResponseAddress(string statusCode, string correlationId, string version) { @@ -90,7 +94,7 @@ public static bool TryParseOperation(string address, Dictionary DirectMethodPrefix.Length) - && (string.CompareOrdinal(address, offset, DirectMethodResponseSegments, 0, DirectMethodResponseSegments.Length) == 0)) + && (string.CompareOrdinal(address, offset, DirectMethodResponseSegments, 0, DirectMethodResponseSegments.Length) == 0)) { operation = Operation.DirectMethodResponse; offset += DirectMethodResponseSegments.Length; @@ -107,6 +111,7 @@ public static bool TryParseOperation(string address, Dictionary ulong.TryParse(correlationValue, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out correlationId); + public static bool IsRequest(Operation operation) + { + switch (operation) + { + case Operation.TwinGetState: + case Operation.TwinPatchReportedState: + case Operation.InvalidTwinRequest: + return true; + case Operation.Unknown: + case Operation.DirectMethodResponse: + return false; + default: + throw new ArgumentOutOfRangeException(nameof(operation), operation, null); + } + } + + // `end` is the index of the last character in the range, inclusive + static StringSegment StringSegmentRange(string buffer, int start, int end) => new StringSegment(buffer, start, end + 1 - start); + + static StringSegment StringSegmentAtOffset(string buffer, int offset) => StringSegmentRange(buffer, offset, buffer.Length - 1); + static bool TryParseProperties(string source, int offset, Dictionary properties) { bool parsingValue = false; @@ -226,30 +253,5 @@ static bool TryParseProperties(string source, int offset, Dictionary - /// This cancellation token will expire when certificate renewal is required. - /// - public CancellationToken Token => this.cts.Token; - public CertificateRenewal(EdgeHubCertificates certificates, ILogger logger) { this.certificates = Preconditions.CheckNotNull(certificates, nameof(certificates)); @@ -48,15 +43,36 @@ public CertificateRenewal(EdgeHubCertificates certificates, ILogger logger) } } + /// + /// This cancellation token will expire when certificate renewal is required. + /// + public CancellationToken Token => this.cts.Token; + public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + try + { + this.timer.Dispose(); + this.cts.Dispose(); + } + catch (OperationCanceledException) + { + // ignore by design + } + } + } + void Callback(object _state) { - TimeSpan timeToExpire = certificates.ServerCertificate.NotAfter - DateTime.UtcNow; + TimeSpan timeToExpire = this.certificates.ServerCertificate.NotAfter - DateTime.UtcNow; if (timeToExpire > TimeBuffer) { // Timer has expired but is not within the time window for renewal @@ -69,7 +85,7 @@ void Callback(object _state) TimeSpan clamped = renewAfter > MaxRenewAfter ? MaxRenewAfter : renewAfter; - logger.LogDebug("Scheduling server certificate renewal timer for {0}.", DateTime.UtcNow.Add(clamped).ToString("o")); + this.logger.LogDebug("Scheduling server certificate renewal timer for {0}.", DateTime.UtcNow.Add(clamped).ToString("o")); this.timer.Change(clamped, Timeout.InfiniteTimeSpan); } else @@ -79,21 +95,5 @@ void Callback(object _state) this.timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); } } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - try - { - this.timer.Dispose(); - this.cts.Dispose(); - } - catch (OperationCanceledException) - { - // ignore by design - } - } - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Constants.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Constants.cs index e688b1ac7bd..45a354b096f 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Constants.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Constants.cs @@ -1,11 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Service { - using System; - using System.Globalization; - public static class Constants { + public const int CertificateValidityDays = 90; + public const string ConfigFileName = "appsettings_hub.json"; + public const string EdgeHubStorageFolder = "edgeHub"; + public const string InitializationVectorFileName = "EdgeHubIV"; + public const string TopicNameConversionSectionName = "mqttTopicNameConversion"; + public const string VersionInfoFileName = "versionInfo.json"; + public const string WorkloadApiVersion = "2018-06-28"; + public static class ConfigKey { public const string DeviceId = "IOTEDGE_DEVICEID"; @@ -22,13 +27,5 @@ public static class ConfigKey public const string EdgeHubDevTrustBundleFile = "EdgeHubDevTrustBundleFile"; public const string EdgeHubClientCertAuthEnabled = "ClientCertAuthEnabled"; } - - public const int CertificateValidityDays = 90; - public const string ConfigFileName = "appsettings_hub.json"; - public const string EdgeHubStorageFolder = "edgeHub"; - public const string InitializationVectorFileName = "EdgeHubIV"; - public const string TopicNameConversionSectionName = "mqttTopicNameConversion"; - public const string VersionInfoFileName = "versionInfo.json"; - public const string WorkloadApiVersion = "2018-06-28"; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/DependencyManager.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/DependencyManager.cs index 6abe6fa06c0..6e28213fbfe 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/DependencyManager.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/DependencyManager.cs @@ -83,6 +83,11 @@ public void Register(ContainerBuilder builder) builder.RegisterModule(new HttpModule()); } + internal static Option GetUpstreamProtocol(IConfigurationRoot configuration) => + Enum.TryParse(configuration.GetValue("UpstreamProtocol", string.Empty), true, out UpstreamProtocol upstreamProtocol) + ? Option.Some(upstreamProtocol) + : Option.None(); + void RegisterAmqpModule(ContainerBuilder builder) { IConfiguration amqpSettings = this.configuration.GetSection("amqpSettings"); @@ -174,11 +179,6 @@ void RegisterCommonModule(ContainerBuilder builder, bool optimizeForPerformance, this.trustBundle)); } - internal static Option GetUpstreamProtocol(IConfigurationRoot configuration) => - Enum.TryParse(configuration.GetValue("UpstreamProtocol", string.Empty), true, out UpstreamProtocol upstreamProtocol) - ? Option.Some(upstreamProtocol) - : Option.None(); - (bool isEnabled, bool usePersistentStorage, StoreAndForwardConfiguration config, string storagePath) GetStoreAndForwardConfiguration() { int defaultTtl = -1; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs index 98c4f3bced3..68d04dc730a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs @@ -13,13 +13,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Service public class EdgeHubCertificates { - - public X509Certificate2 ServerCertificate { get; } - - public IList CertificateChain { get; } - - public IList TrustBundle { get; } - EdgeHubCertificates(X509Certificate2 serverCertificate, IList certificateChain, IList trustBundle) { this.ServerCertificate = Preconditions.CheckNotNull(serverCertificate, nameof(serverCertificate)); @@ -27,6 +20,12 @@ public class EdgeHubCertificates this.TrustBundle = Preconditions.CheckNotNull(trustBundle, nameof(trustBundle)); } + public X509Certificate2 ServerCertificate { get; } + + public IList CertificateChain { get; } + + public IList TrustBundle { get; } + public static async Task LoadAsync(IConfigurationRoot configuration) { Preconditions.CheckNotNull(configuration, nameof(configuration)); @@ -54,9 +53,10 @@ public static async Task LoadAsync(IConfigurationRoot confi InstallCertificates(certificates.CertificateChain); IEnumerable trustBundle = await CertificateHelper.GetTrustBundleFromEdgelet(workloadUri, Constants.WorkloadApiVersion, moduleId, generationId); - result = new EdgeHubCertificates(certificates.ServerCertificate, - certificates.CertificateChain?.ToList(), - trustBundle?.ToList()); + result = new EdgeHubCertificates( + certificates.ServerCertificate, + certificates.CertificateChain?.ToList(), + trustBundle?.ToList()); } else if (!string.IsNullOrEmpty(edgeHubDevCertPath) && !string.IsNullOrEmpty(edgeHubDevPrivateKeyPath) && @@ -70,9 +70,10 @@ public static async Task LoadAsync(IConfigurationRoot confi IEnumerable trustBundle = CertificateHelper.ParseTrustedBundleFromFile(edgeHubDevTrustBundlePath); - result = new EdgeHubCertificates(certificates.ServerCertificate, - certificates.CertificateChain?.ToList(), - trustBundle?.ToList()); + result = new EdgeHubCertificates( + certificates.ServerCertificate, + certificates.CertificateChain?.ToList(), + trustBundle?.ToList()); } else if (!string.IsNullOrEmpty(edgeHubDockerCertPFXPath) && !string.IsNullOrEmpty(edgeHubDockerCaChainCertPath)) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Hosting.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Hosting.cs index 25337ddd9da..e3545a47775 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Hosting.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Hosting.cs @@ -7,9 +7,9 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Service using Autofac; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; + using Microsoft.Azure.Devices.Edge.Hub.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Microsoft.Azure.Devices.Edge.Hub.Http.Extensions; public class Hosting { @@ -19,6 +19,10 @@ public class Hosting this.Container = container; } + public IContainer Container { get; } + + public IWebHost WebHost { get; } + public static Hosting Initialize( IConfigurationRoot configuration, X509Certificate2 serverCertificate, @@ -28,33 +32,34 @@ public static Hosting Initialize( int port = configuration.GetValue("httpSettings:port", 443); var certificateMode = clientCertAuthEnabled ? ClientCertificateMode.AllowCertificate : ClientCertificateMode.NoCertificate; IWebHostBuilder webHostBuilder = new WebHostBuilder() - .UseKestrel(options => - { - options.Listen(!Socket.OSSupportsIPv6 ? IPAddress.Any : IPAddress.IPv6Any, port, listenOptions => + .UseKestrel( + options => { - listenOptions.UseHttpsExtensions( - new HttpsConnectionAdapterOptions() + options.Listen( + !Socket.OSSupportsIPv6 ? IPAddress.Any : IPAddress.IPv6Any, + port, + listenOptions => { - ServerCertificate = serverCertificate, - ClientCertificateValidation = (clientCert, chain, policyErrors) => true, - ClientCertificateMode = certificateMode + listenOptions.UseHttpsExtensions( + new HttpsConnectionAdapterOptions() + { + ServerCertificate = serverCertificate, + ClientCertificateValidation = (clientCert, chain, policyErrors) => true, + ClientCertificateMode = certificateMode + }); }); - }); - }) + }) .UseSockets() - .ConfigureServices(serviceCollection => - { - serviceCollection.AddSingleton(configuration); - serviceCollection.AddSingleton(dependencyManager); - }) + .ConfigureServices( + serviceCollection => + { + serviceCollection.AddSingleton(configuration); + serviceCollection.AddSingleton(dependencyManager); + }) .UseStartup(); IWebHost webHost = webHostBuilder.Build(); IContainer container = webHost.Services.GetService(typeof(IStartup)) is Startup startup ? startup.Container : null; return new Hosting(webHost, container); } - - public IContainer Container { get; } - - public IWebHost WebHost { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Microsoft.Azure.Devices.Edge.Hub.Service.csproj b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Microsoft.Azure.Devices.Edge.Hub.Service.csproj index ef92bdfdfb8..e67a310ea16 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Microsoft.Azure.Devices.Edge.Hub.Service.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Microsoft.Azure.Devices.Edge.Hub.Service.csproj @@ -68,4 +68,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Startup.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Startup.cs index 42ac246b93e..72c380f2137 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Startup.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Startup.cs @@ -39,18 +39,8 @@ public IServiceProvider ConfigureServices(IServiceCollection services) services.AddMvc(options => options.Filters.Add(typeof(ExceptionFilter))); services.Configure(options => { options.Filters.Add(new RequireHttpsAttribute()); }); this.Container = this.BuildContainer(services); - - return new AutofacServiceProvider(this.Container); - } - IContainer BuildContainer(IServiceCollection services) - { - var builder = new ContainerBuilder(); - builder.Populate(services); - this.dependencyManager.Register(builder); - builder.RegisterInstance(this); - - return builder.Build(); + return new AutofacServiceProvider(this.Container); } public void Configure(IApplicationBuilder app) @@ -77,14 +67,25 @@ public void Configure(IApplicationBuilder app) app.UseAuthenticationMiddleware(iotHubHostname, edgeDeviceId); - app.Use(async (context, next) => - { - // Response header is added to prevent MIME type sniffing - context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); - await next(); - }); + app.Use( + async (context, next) => + { + // Response header is added to prevent MIME type sniffing + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + await next(); + }); app.UseMvc(); } + + IContainer BuildContainer(IServiceCollection services) + { + var builder = new ContainerBuilder(); + builder.Populate(services); + this.dependencyManager.Register(builder); + builder.RegisterInstance(this); + + return builder.Build(); + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/AmqpModule.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/AmqpModule.cs index 7e5fac098e9..7e56634df34 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/AmqpModule.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/AmqpModule.cs @@ -43,13 +43,14 @@ public AmqpModule( protected override void Load(ContainerBuilder builder) { // ITransportSettings - builder.Register(async c => - { - IClientCredentialsFactory clientCredentialsProvider = c.Resolve(); - IAuthenticator authenticator = await c.Resolve>(); - ITransportSettings settings = new DefaultTransportSettings(this.scheme, HostName, this.port, this.tlsCertificate, this.clientCertAuthAllowed, authenticator, clientCredentialsProvider); - return settings; - }) + builder.Register( + async c => + { + IClientCredentialsFactory clientCredentialsProvider = c.Resolve(); + IAuthenticator authenticator = await c.Resolve>(); + ITransportSettings settings = new DefaultTransportSettings(this.scheme, HostName, this.port, this.tlsCertificate, this.clientCertAuthAllowed, authenticator, clientCredentialsProvider); + return settings; + }) .As>() .SingleInstance(); @@ -60,50 +61,50 @@ protected override void Load(ContainerBuilder builder) // ILinkHandlerProvider builder.Register( - c => - { - IMessageConverter messageConverter = new AmqpMessageConverter(); - IMessageConverter twinMessageConverter = new AmqpTwinMessageConverter(); - IMessageConverter directMethodMessageConverter = new AmqpDirectMethodMessageConverter(); - var identityProvider = c.Resolve(); - ILinkHandlerProvider linkHandlerProvider = new LinkHandlerProvider(messageConverter, twinMessageConverter, directMethodMessageConverter, identityProvider); - return linkHandlerProvider; - }) + c => + { + IMessageConverter messageConverter = new AmqpMessageConverter(); + IMessageConverter twinMessageConverter = new AmqpTwinMessageConverter(); + IMessageConverter directMethodMessageConverter = new AmqpDirectMethodMessageConverter(); + var identityProvider = c.Resolve(); + ILinkHandlerProvider linkHandlerProvider = new LinkHandlerProvider(messageConverter, twinMessageConverter, directMethodMessageConverter, identityProvider); + return linkHandlerProvider; + }) .As() .SingleInstance(); // Task builder.Register( - async c => - { - var identityFactory = c.Resolve(); - var transportSettingsTask= c.Resolve>(); - var transportListenerProvider = c.Resolve(); - var linkHandlerProvider = c.Resolve(); - var credentialsCacheTask = c.Resolve>(); - var authenticatorTask = c.Resolve>(); - var connectionProviderTask = c.Resolve>(); - ICredentialsCache credentialsCache = await credentialsCacheTask; - IAuthenticator authenticator = await authenticatorTask; - IConnectionProvider connectionProvider = await connectionProviderTask; - ITransportSettings transportSettings = await transportSettingsTask; - var webSocketListenerRegistry = c.Resolve(); - AmqpSettings amqpSettings = AmqpSettingsProvider.GetDefaultAmqpSettings( - this.iotHubHostName, - authenticator, - identityFactory, - linkHandlerProvider, - connectionProvider, - credentialsCache); + async c => + { + var identityFactory = c.Resolve(); + var transportSettingsTask = c.Resolve>(); + var transportListenerProvider = c.Resolve(); + var linkHandlerProvider = c.Resolve(); + var credentialsCacheTask = c.Resolve>(); + var authenticatorTask = c.Resolve>(); + var connectionProviderTask = c.Resolve>(); + ICredentialsCache credentialsCache = await credentialsCacheTask; + IAuthenticator authenticator = await authenticatorTask; + IConnectionProvider connectionProvider = await connectionProviderTask; + ITransportSettings transportSettings = await transportSettingsTask; + var webSocketListenerRegistry = c.Resolve(); + AmqpSettings amqpSettings = AmqpSettingsProvider.GetDefaultAmqpSettings( + this.iotHubHostName, + authenticator, + identityFactory, + linkHandlerProvider, + connectionProvider, + credentialsCache); - return new AmqpProtocolHead( - transportSettings, - amqpSettings, - transportListenerProvider, - webSocketListenerRegistry, - authenticator, - identityFactory); - }) + return new AmqpProtocolHead( + transportSettings, + amqpSettings, + transportListenerProvider, + webSocketListenerRegistry, + authenticator, + identityFactory); + }) .As>() .SingleInstance(); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/CommonModule.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/CommonModule.cs index 00a208afd41..8b9dbc1eac8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/CommonModule.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/CommonModule.cs @@ -12,9 +12,11 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Service.Modules using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Storage; + using Microsoft.Azure.Devices.Edge.Storage.RocksDb; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Edged; using Microsoft.Extensions.Logging; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Service.Constants; public class CommonModule : Module { @@ -85,7 +87,7 @@ protected override void Load(ContainerBuilder builder) { string edgeHubGenerationId = this.edgeHubGenerationId.Expect(() => new InvalidOperationException("Generation ID missing")); string workloadUri = this.workloadUri.Expect(() => new InvalidOperationException("workloadUri is missing")); - return new HttpHsmSignatureProvider(this.edgeHubModuleId, edgeHubGenerationId, workloadUri, Service.Constants.WorkloadApiVersion) as ISignatureProvider; + return new HttpHsmSignatureProvider(this.edgeHubModuleId, edgeHubGenerationId, workloadUri, Constants.WorkloadApiVersion) as ISignatureProvider; }); return signatureProvider; }) @@ -98,40 +100,42 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); // DataBase options - builder.Register(c => new Storage.RocksDb.RocksDbOptionsProvider(c.Resolve(), this.optimizeForPerformance)) - .As() + builder.Register(c => new RocksDbOptionsProvider(c.Resolve(), this.optimizeForPerformance)) + .As() .SingleInstance(); // IDbStoreProvider builder.Register( - c => - { - var loggerFactory = c.Resolve(); - ILogger logger = loggerFactory.CreateLogger(typeof(RoutingModule)); - - if (this.usePersistentStorage) + c => { - // Create partitions for messages and twins - var partitionsList = new List { Constants.MessageStorePartitionKey, Constants.TwinStorePartitionKey, Core.Constants.CheckpointStorePartitionKey }; - try + var loggerFactory = c.Resolve(); + ILogger logger = loggerFactory.CreateLogger(typeof(RoutingModule)); + + if (this.usePersistentStorage) { - IDbStoreProvider dbStoreprovider = Storage.RocksDb.DbStoreProvider.Create(c.Resolve(), - this.storagePath, partitionsList); - logger.LogInformation($"Created persistent store at {this.storagePath}"); - return dbStoreprovider; + // Create partitions for messages and twins + var partitionsList = new List { Core.Constants.MessageStorePartitionKey, Core.Constants.TwinStorePartitionKey, Core.Constants.CheckpointStorePartitionKey }; + try + { + IDbStoreProvider dbStoreprovider = DbStoreProvider.Create( + c.Resolve(), + this.storagePath, + partitionsList); + logger.LogInformation($"Created persistent store at {this.storagePath}"); + return dbStoreprovider; + } + catch (Exception ex) when (!ExceptionEx.IsFatal(ex)) + { + logger.LogError(ex, "Error creating RocksDB store. Falling back to in-memory store."); + return new InMemoryDbStoreProvider(); + } } - catch (Exception ex) when (!ExceptionEx.IsFatal(ex)) + else { - logger.LogError(ex, "Error creating RocksDB store. Falling back to in-memory store."); + logger.LogInformation($"Using in-memory store"); return new InMemoryDbStoreProvider(); } - } - else - { - logger.LogInformation($"Using in-memory store"); - return new InMemoryDbStoreProvider(); - } - }) + }) .As() .SingleInstance(); @@ -146,10 +150,10 @@ protected override void Load(ContainerBuilder builder) var encryptionProvider = await EncryptionProvider.CreateAsync( this.storagePath, new Uri(uri), - Service.Constants.WorkloadApiVersion, + Constants.WorkloadApiVersion, this.edgeHubModuleId, this.edgeHubGenerationId.Expect(() => new InvalidOperationException("Missing generation ID")), - Service.Constants.InitializationVectorFileName) as IEncryptionProvider; + Constants.InitializationVectorFileName) as IEncryptionProvider; return Option.Some(encryptionProvider); }) .GetOrElse(() => Task.FromResult(Option.None())); @@ -169,12 +173,13 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); // ITokenProvider - builder.Register(c => - { - string deviceId = WebUtility.UrlEncode(this.edgeDeviceId); - string moduleId = WebUtility.UrlEncode(this.edgeHubModuleId); - return new ClientTokenProvider(c.Resolve(), this.iothubHostName, deviceId, moduleId, TimeSpan.FromHours(1)); - }) + builder.Register( + c => + { + string deviceId = WebUtility.UrlEncode(this.edgeDeviceId); + string moduleId = WebUtility.UrlEncode(this.edgeHubModuleId); + return new ClientTokenProvider(c.Resolve(), this.iothubHostName, deviceId, moduleId, TimeSpan.FromHours(1)); + }) .Named("EdgeHubServiceAuthTokenProvider") .SingleInstance(); @@ -203,53 +208,56 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); // Task - builder.Register(async c => - { - ICredentialsCache underlyingCredentialsCache; - if (this.persistTokens) - { - IKeyValueStore encryptedStore = await GetEncryptedStore(c, "CredentialsCache"); - return new PersistedTokenCredentialsCache(encryptedStore); - } - else + builder.Register( + async c => { - underlyingCredentialsCache = new NullCredentialsCache(); - } - ICredentialsCache credentialsCache = new CredentialsCache(underlyingCredentialsCache); - return credentialsCache; - }) + ICredentialsCache underlyingCredentialsCache; + if (this.persistTokens) + { + IKeyValueStore encryptedStore = await GetEncryptedStore(c, "CredentialsCache"); + return new PersistedTokenCredentialsCache(encryptedStore); + } + else + { + underlyingCredentialsCache = new NullCredentialsCache(); + } + + ICredentialsCache credentialsCache = new CredentialsCache(underlyingCredentialsCache); + return credentialsCache; + }) .As>() .SingleInstance(); // Task - builder.Register(async c => - { - IAuthenticator tokenAuthenticator; - IAuthenticator certificateAuthenticator; - IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache; - var credentialsCacheTask = c.Resolve>(); - // by default regardless of how the authenticationMode, X.509 certificate validation will always be scoped - deviceScopeIdentitiesCache = await c.Resolve>(); - certificateAuthenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache, new NullAuthenticator(), this.trustBundle, true); - switch (this.authenticationMode) + builder.Register( + async c => { - case AuthenticationMode.Cloud: - tokenAuthenticator = await this.GetCloudTokenAuthenticator(c); - break; + IAuthenticator tokenAuthenticator; + IAuthenticator certificateAuthenticator; + IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache; + var credentialsCacheTask = c.Resolve>(); + // by default regardless of how the authenticationMode, X.509 certificate validation will always be scoped + deviceScopeIdentitiesCache = await c.Resolve>(); + certificateAuthenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache, new NullAuthenticator(), this.trustBundle, true); + switch (this.authenticationMode) + { + case AuthenticationMode.Cloud: + tokenAuthenticator = await this.GetCloudTokenAuthenticator(c); + break; - case AuthenticationMode.Scope: - tokenAuthenticator = new DeviceScopeTokenAuthenticator(deviceScopeIdentitiesCache, this.iothubHostName, this.edgeDeviceHostName, new NullAuthenticator(), true, true); - break; + case AuthenticationMode.Scope: + tokenAuthenticator = new DeviceScopeTokenAuthenticator(deviceScopeIdentitiesCache, this.iothubHostName, this.edgeDeviceHostName, new NullAuthenticator(), true, true); + break; - default: - IAuthenticator cloudTokenAuthenticator = await this.GetCloudTokenAuthenticator(c); - tokenAuthenticator = new DeviceScopeTokenAuthenticator(deviceScopeIdentitiesCache, this.iothubHostName, this.edgeDeviceHostName, cloudTokenAuthenticator, true, true); - break; - } + default: + IAuthenticator cloudTokenAuthenticator = await this.GetCloudTokenAuthenticator(c); + tokenAuthenticator = new DeviceScopeTokenAuthenticator(deviceScopeIdentitiesCache, this.iothubHostName, this.edgeDeviceHostName, cloudTokenAuthenticator, true, true); + break; + } - ICredentialsCache credentialsCache = await credentialsCacheTask; - return new Authenticator(tokenAuthenticator, certificateAuthenticator, credentialsCache) as IAuthenticator; - }) + ICredentialsCache credentialsCache = await credentialsCacheTask; + return new Authenticator(tokenAuthenticator, certificateAuthenticator, credentialsCache) as IAuthenticator; + }) .As>() .SingleInstance(); @@ -259,34 +267,51 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); // ConnectionReauthenticator - builder.Register(async c => - { - var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); - var connectionManagerTask = c.Resolve>(); - var authenticatorTask = c.Resolve>(); - var credentialsCacheTask = c.Resolve>(); - var deviceScopeIdentitiesCacheTask = c.Resolve>(); - var deviceConnectivityManager = c.Resolve(); - IConnectionManager connectionManager = await connectionManagerTask; - IAuthenticator authenticator = await authenticatorTask; - ICredentialsCache credentialsCache = await credentialsCacheTask; - IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache = await deviceScopeIdentitiesCacheTask; - var connectionReauthenticator = new ConnectionReauthenticator( - connectionManager, - authenticator, - credentialsCache, - deviceScopeIdentitiesCache, - TimeSpan.FromMinutes(5), - edgeHubCredentials.Identity, - deviceConnectivityManager); - return connectionReauthenticator; - }) + builder.Register( + async c => + { + var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); + var connectionManagerTask = c.Resolve>(); + var authenticatorTask = c.Resolve>(); + var credentialsCacheTask = c.Resolve>(); + var deviceScopeIdentitiesCacheTask = c.Resolve>(); + var deviceConnectivityManager = c.Resolve(); + IConnectionManager connectionManager = await connectionManagerTask; + IAuthenticator authenticator = await authenticatorTask; + ICredentialsCache credentialsCache = await credentialsCacheTask; + IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache = await deviceScopeIdentitiesCacheTask; + var connectionReauthenticator = new ConnectionReauthenticator( + connectionManager, + authenticator, + credentialsCache, + deviceScopeIdentitiesCache, + TimeSpan.FromMinutes(5), + edgeHubCredentials.Identity, + deviceConnectivityManager); + return connectionReauthenticator; + }) .As>() .SingleInstance(); base.Load(builder); } + static async Task> GetEncryptedStore(IComponentContext context, string entityName) + { + var storeProvider = context.Resolve(); + Option encryptionProvider = await context.Resolve>>(); + IKeyValueStore encryptedStore = encryptionProvider + .Map( + e => + { + IEntityStore entityStore = storeProvider.GetEntityStore(entityName); + IKeyValueStore es = new EncryptedStore(entityStore, e); + return es; + }) + .GetOrElse(() => new NullKeyValueStore() as IKeyValueStore); + return encryptedStore; + } + async Task GetCloudTokenAuthenticator(IComponentContext context) { IAuthenticator tokenAuthenticator; @@ -306,21 +331,5 @@ async Task GetCloudTokenAuthenticator(IComponentContext context) return tokenAuthenticator; } - - static async Task> GetEncryptedStore(IComponentContext context, string entityName) - { - var storeProvider = context.Resolve(); - Option encryptionProvider = await context.Resolve>>(); - IKeyValueStore encryptedStore = encryptionProvider - .Map( - e => - { - IEntityStore entityStore = storeProvider.GetEntityStore(entityName); - IKeyValueStore es = new EncryptedStore(entityStore, e); - return es; - }) - .GetOrElse(() => new NullKeyValueStore() as IKeyValueStore); - return encryptedStore; - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/MqttModule.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/MqttModule.cs index dab1dc08481..bcb28d8e743 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/MqttModule.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/MqttModule.cs @@ -1,28 +1,28 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Service.Modules { - using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Autofac; using DotNetty.Buffers; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; - using Microsoft.Azure.Devices.Edge.Hub.Http; using Microsoft.Azure.Devices.Edge.Hub.Mqtt; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.ProtocolGateway; - using Microsoft.Azure.Devices.ProtocolGateway.Identity; using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; using Microsoft.Extensions.Configuration; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Core.Constants; using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; public class MqttModule : Module { readonly MessageAddressConversionConfiguration conversionConfiguration; + readonly IConfiguration mqttSettingsConfiguration; - //TODO: This causes reSharperWarning. Remove this TODO once code below are uncommented. + + // TODO: This causes reSharperWarning. Remove this TODO once code below are uncommented. // ReSharper disable once NotAccessedField.Local readonly bool isStoreAndForwardEnabled; readonly X509Certificate2 tlsCertificate; @@ -48,11 +48,12 @@ public MqttModule( protected override void Load(ContainerBuilder builder) { // IByteBufferAllocator - builder.Register(c => - { - // TODO - We should probably also use some heuristics to make this determination, like how much memory does the system have. - return this.optimizeForPerformance ? PooledByteBufferAllocator.Default : UnpooledByteBufferAllocator.Default as IByteBufferAllocator; - }) + builder.Register( + c => + { + // TODO - We should probably also use some heuristics to make this determination, like how much memory does the system have. + return this.optimizeForPerformance ? PooledByteBufferAllocator.Default : UnpooledByteBufferAllocator.Default as IByteBufferAllocator; + }) .As() .SingleInstance(); @@ -77,14 +78,14 @@ protected override void Load(ContainerBuilder builder) // Task builder.Register( - async c => - { - var pgMessageConverter = c.Resolve>(); - var byteBufferConverter = c.Resolve(); - IConnectionProvider connectionProvider = await c.Resolve>(); - IMqttConnectionProvider mqtt = new MqttConnectionProvider(connectionProvider, pgMessageConverter, byteBufferConverter); - return mqtt; - }) + async c => + { + var pgMessageConverter = c.Resolve>(); + var byteBufferConverter = c.Resolve(); + IConnectionProvider connectionProvider = await c.Resolve>(); + IMqttConnectionProvider mqtt = new MqttConnectionProvider(connectionProvider, pgMessageConverter, byteBufferConverter); + return mqtt; + }) .As>() .SingleInstance(); @@ -94,7 +95,7 @@ protected override void Load(ContainerBuilder builder) { if (this.isStoreAndForwardEnabled) { - IEntityStore entityStore = new StoreProvider(c.Resolve()).GetEntityStore(Core.Constants.SessionStorePartitionKey); + IEntityStore entityStore = new StoreProvider(c.Resolve()).GetEntityStore(Constants.SessionStorePartitionKey); IEdgeHub edgeHub = await c.Resolve>(); return new SessionStateStoragePersistenceProvider(edgeHub, entityStore) as ISessionStatePersistenceProvider; } @@ -109,20 +110,20 @@ protected override void Load(ContainerBuilder builder) // MqttProtocolHead builder.Register( - async c => - { - var settingsProvider = c.Resolve(); - var websocketListenerRegistry = c.Resolve(); - var byteBufferAllocator = c.Resolve(); - var mqttConnectionProviderTask = c.Resolve>(); - var sessionStatePersistenceProviderTask = c.Resolve>(); - var authenticatorProviderTask = c.Resolve>(); - IClientCredentialsFactory clientCredentialsProvider = c.Resolve(); - IMqttConnectionProvider mqttConnectionProvider = await mqttConnectionProviderTask; - ISessionStatePersistenceProvider sessionStatePersistenceProvider = await sessionStatePersistenceProviderTask; - IAuthenticator authenticator = await authenticatorProviderTask; - return new MqttProtocolHead( - settingsProvider, + async c => + { + var settingsProvider = c.Resolve(); + var websocketListenerRegistry = c.Resolve(); + var byteBufferAllocator = c.Resolve(); + var mqttConnectionProviderTask = c.Resolve>(); + var sessionStatePersistenceProviderTask = c.Resolve>(); + var authenticatorProviderTask = c.Resolve>(); + IClientCredentialsFactory clientCredentialsProvider = c.Resolve(); + IMqttConnectionProvider mqttConnectionProvider = await mqttConnectionProviderTask; + ISessionStatePersistenceProvider sessionStatePersistenceProvider = await sessionStatePersistenceProviderTask; + IAuthenticator authenticator = await authenticatorProviderTask; + return new MqttProtocolHead( + settingsProvider, this.tlsCertificate, mqttConnectionProvider, authenticator, @@ -131,7 +132,7 @@ protected override void Load(ContainerBuilder builder) websocketListenerRegistry, byteBufferAllocator, this.clientCertAuthAllowed); - }) + }) .As>() .SingleInstance(); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/RoutingModule.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/RoutingModule.cs index 18df64eebe3..4c4f1fba0e1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/RoutingModule.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/modules/RoutingModule.cs @@ -20,8 +20,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Service.Modules using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Shared; - using IRoutingMessage = Routing.Core.IMessage; - using Message = Client.Message; + using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; + using Message = Microsoft.Azure.Devices.Client.Message; public class RoutingModule : Module { @@ -42,7 +42,8 @@ public class RoutingModule : Module readonly bool closeCloudConnectionOnIdleTimeout; readonly TimeSpan operationTimeout; - public RoutingModule(string iotHubName, + public RoutingModule( + string iotHubName, string edgeDeviceId, string edgeModuleId, Option connectionString, @@ -86,33 +87,33 @@ protected override void Load(ContainerBuilder builder) // IRoutingPerfCounter builder.Register( - c => - { - Routing.PerfCounter = NullRoutingPerfCounter.Instance; - return Routing.PerfCounter; - }) + c => + { + Routing.PerfCounter = NullRoutingPerfCounter.Instance; + return Routing.PerfCounter; + }) .As() .AutoActivate() .SingleInstance(); // IRoutingUserAnalyticsLogger builder.Register( - c => - { - Routing.UserAnalyticsLogger = NullUserAnalyticsLogger.Instance; - return Routing.UserAnalyticsLogger; - }) + c => + { + Routing.UserAnalyticsLogger = NullUserAnalyticsLogger.Instance; + return Routing.UserAnalyticsLogger; + }) .As() .AutoActivate() .SingleInstance(); // IRoutingUserMetricLogger builder.Register( - c => - { - Routing.UserMetricLogger = NullRoutingUserMetricLogger.Instance; - return Routing.UserMetricLogger; - }) + c => + { + Routing.UserMetricLogger = NullRoutingUserMetricLogger.Instance; + return Routing.UserMetricLogger; + }) .As() .AutoActivate() .SingleInstance(); @@ -134,62 +135,64 @@ protected override void Load(ContainerBuilder builder) // IMessageConverterProvider builder.Register( - c => new MessageConverterProvider(new Dictionary() - { - { typeof(Message), c.Resolve>() }, - { typeof(Twin), c.Resolve>() }, - { typeof(TwinCollection), c.Resolve>() } - })) + c => new MessageConverterProvider( + new Dictionary() + { + { typeof(Message), c.Resolve>() }, + { typeof(Twin), c.Resolve>() }, + { typeof(TwinCollection), c.Resolve>() } + })) .As() .SingleInstance(); // IDeviceConnectivityManager builder.Register( - c => - { - var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); - IDeviceConnectivityManager deviceConnectivityManager = new DeviceConnectivityManager(this.connectivityCheckFrequency, TimeSpan.FromMinutes(2), edgeHubCredentials.Identity); - return deviceConnectivityManager; - }) + c => + { + var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); + IDeviceConnectivityManager deviceConnectivityManager = new DeviceConnectivityManager(this.connectivityCheckFrequency, TimeSpan.FromMinutes(2), edgeHubCredentials.Identity); + return deviceConnectivityManager; + }) .As() .SingleInstance(); // IDeviceClientProvider - builder.Register(c => - { - IClientProvider underlyingClientProvider = new ClientProvider(); - IClientProvider connectivityAwareClientProvider = new ConnectivityAwareClientProvider(underlyingClientProvider, c.Resolve()); - return connectivityAwareClientProvider; - }) + builder.Register( + c => + { + IClientProvider underlyingClientProvider = new ClientProvider(); + IClientProvider connectivityAwareClientProvider = new ConnectivityAwareClientProvider(underlyingClientProvider, c.Resolve()); + return connectivityAwareClientProvider; + }) .As() .SingleInstance(); // Task builder.Register( - async c => - { - var messageConverterProvider = c.Resolve(); - var clientProvider = c.Resolve(); - var tokenProvider = c.ResolveNamed("EdgeHubClientAuthTokenProvider"); - var credentialsCacheTask = c.Resolve>(); - var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); - var deviceScopeIdentitiesCacheTask = c.Resolve>(); - IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache = await deviceScopeIdentitiesCacheTask; - ICredentialsCache credentialsCache = await credentialsCacheTask; - ICloudConnectionProvider cloudConnectionProvider = new CloudConnectionProvider( - messageConverterProvider, - this.connectionPoolSize, - clientProvider, - this.upstreamProtocol, - tokenProvider, - deviceScopeIdentitiesCache, - credentialsCache, - edgeHubCredentials.Identity, - this.cloudConnectionIdleTimeout, - this.closeCloudConnectionOnIdleTimeout, - this.operationTimeout); - return cloudConnectionProvider; - }) + async c => + { + var messageConverterProvider = c.Resolve(); + var clientProvider = c.Resolve(); + var tokenProvider = c.ResolveNamed("EdgeHubClientAuthTokenProvider"); + var credentialsCacheTask = c.Resolve>(); + var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); + var deviceScopeIdentitiesCacheTask = c.Resolve>(); + IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache = await deviceScopeIdentitiesCacheTask; + ICredentialsCache credentialsCache = await credentialsCacheTask; + ICloudConnectionProvider cloudConnectionProvider = new CloudConnectionProvider( + messageConverterProvider, + this.connectionPoolSize, + clientProvider, + this.upstreamProtocol, + tokenProvider, + deviceScopeIdentitiesCache, + credentialsCache, + edgeHubCredentials.Identity, + this.cloudConnectionIdleTimeout, + this.closeCloudConnectionOnIdleTimeout, + this.operationTimeout); + return cloudConnectionProvider; + }) .As>() .SingleInstance(); @@ -200,30 +203,31 @@ protected override void Load(ContainerBuilder builder) // Task builder.Register( - async c => - { - var cloudConnectionProviderTask = c.Resolve>(); - var credentialsCacheTask = c.Resolve>(); - var identityProvider = c.Resolve(); - ICloudConnectionProvider cloudConnectionProvider = await cloudConnectionProviderTask; - ICredentialsCache credentialsCache = await credentialsCacheTask; - IConnectionManager connectionManager = new ConnectionManager( - cloudConnectionProvider, - credentialsCache, - identityProvider, - this.maxConnectedClients); - return connectionManager; - }) + async c => + { + var cloudConnectionProviderTask = c.Resolve>(); + var credentialsCacheTask = c.Resolve>(); + var identityProvider = c.Resolve(); + ICloudConnectionProvider cloudConnectionProvider = await cloudConnectionProviderTask; + ICredentialsCache credentialsCache = await credentialsCacheTask; + IConnectionManager connectionManager = new ConnectionManager( + cloudConnectionProvider, + credentialsCache, + identityProvider, + this.maxConnectedClients); + return connectionManager; + }) .As>() .SingleInstance(); // Task - builder.Register(async c => - { - var messageConverter = c.Resolve>(); - IConnectionManager connectionManager = await c.Resolve>(); - return new EndpointFactory(connectionManager, messageConverter, this.edgeDeviceId) as IEndpointFactory; - }) + builder.Register( + async c => + { + var messageConverter = c.Resolve>(); + IConnectionManager connectionManager = await c.Resolve>(); + return new EndpointFactory(connectionManager, messageConverter, this.edgeDeviceId) as IEndpointFactory; + }) .As>() .SingleInstance(); @@ -241,13 +245,13 @@ protected override void Load(ContainerBuilder builder) { // EndpointExecutorConfig builder.Register( - c => - { - RetryStrategy defaultRetryStrategy = new FixedInterval(0, TimeSpan.FromSeconds(1)); - TimeSpan defaultRevivePeriod = TimeSpan.FromHours(1); - TimeSpan defaultTimeout = TimeSpan.FromSeconds(60); - return new EndpointExecutorConfig(defaultTimeout, defaultRetryStrategy, defaultRevivePeriod, true); - }) + c => + { + RetryStrategy defaultRetryStrategy = new FixedInterval(0, TimeSpan.FromSeconds(1)); + TimeSpan defaultRevivePeriod = TimeSpan.FromHours(1); + TimeSpan defaultTimeout = TimeSpan.FromSeconds(60); + return new EndpointExecutorConfig(defaultTimeout, defaultRetryStrategy, defaultRevivePeriod, true); + }) .As() .SingleInstance(); @@ -258,23 +262,24 @@ protected override void Load(ContainerBuilder builder) // Task builder.Register( - async c => - { - var endpointExecutorFactory = c.Resolve(); - var routerConfig = c.Resolve(); - Router router = await Router.CreateAsync(Guid.NewGuid().ToString(), this.iotHubName, routerConfig, endpointExecutorFactory); - return router; - }) + async c => + { + var endpointExecutorFactory = c.Resolve(); + var routerConfig = c.Resolve(); + Router router = await Router.CreateAsync(Guid.NewGuid().ToString(), this.iotHubName, routerConfig, endpointExecutorFactory); + return router; + }) .As>() .SingleInstance(); // Task - builder.Register(async c => - { - var messageConverterProvider = c.Resolve(); - IConnectionManager connectionManager = await c.Resolve>(); - return TwinManager.CreateTwinManager(connectionManager, messageConverterProvider, Option.None()); - }) + builder.Register( + async c => + { + var messageConverterProvider = c.Resolve(); + IConnectionManager connectionManager = await c.Resolve>(); + return TwinManager.CreateTwinManager(connectionManager, messageConverterProvider, Option.None()); + }) .As>() .SingleInstance(); } @@ -282,106 +287,108 @@ protected override void Load(ContainerBuilder builder) { // EndpointExecutorConfig builder.Register( - c => - { - // Endpoint executor config values - - // ExponentialBackoff - minBackoff = 1s, maxBackoff = 60s, delta (used to add randomness to backoff) - 1s (default) - // Num of retries = int.MaxValue(we want to keep retrying till the message is sent) - // Revive period - period for which the endpoint should be considered dead if it doesn't respond - 1 min (we want to try continuously till the message expires) - // Timeout - time for which we want for the ack from the endpoint = 30s - // TODO - Should the number of retries be tied to the Store and Forward ttl? Not - // doing that right now as that value can be changed at runtime, but these settings - // cannot. Need to make the number of retries dynamically configurable for that. - - TimeSpan minWait = TimeSpan.FromSeconds(1); - TimeSpan maxWait = TimeSpan.FromSeconds(60); - TimeSpan delta = TimeSpan.FromSeconds(1); - int retries = int.MaxValue; - RetryStrategy retryStrategy = new ExponentialBackoff(retries, minWait, maxWait, delta); - TimeSpan timeout = TimeSpan.FromSeconds(30); - TimeSpan revivePeriod = TimeSpan.FromSeconds(30); - return new EndpointExecutorConfig(timeout, retryStrategy, revivePeriod); - }) + c => + { + // Endpoint executor config values - + // ExponentialBackoff - minBackoff = 1s, maxBackoff = 60s, delta (used to add randomness to backoff) - 1s (default) + // Num of retries = int.MaxValue(we want to keep retrying till the message is sent) + // Revive period - period for which the endpoint should be considered dead if it doesn't respond - 1 min (we want to try continuously till the message expires) + // Timeout - time for which we want for the ack from the endpoint = 30s + // TODO - Should the number of retries be tied to the Store and Forward ttl? Not + // doing that right now as that value can be changed at runtime, but these settings + // cannot. Need to make the number of retries dynamically configurable for that. + TimeSpan minWait = TimeSpan.FromSeconds(1); + TimeSpan maxWait = TimeSpan.FromSeconds(60); + TimeSpan delta = TimeSpan.FromSeconds(1); + int retries = int.MaxValue; + RetryStrategy retryStrategy = new ExponentialBackoff(retries, minWait, maxWait, delta); + TimeSpan timeout = TimeSpan.FromSeconds(30); + TimeSpan revivePeriod = TimeSpan.FromSeconds(30); + return new EndpointExecutorConfig(timeout, retryStrategy, revivePeriod); + }) .As() .SingleInstance(); // ICheckpointStore - builder.Register(c => - { - var dbStoreProvider = c.Resolve(); - IStoreProvider storeProvider = new StoreProvider(dbStoreProvider); - return CheckpointStore.Create(storeProvider); - }) + builder.Register( + c => + { + var dbStoreProvider = c.Resolve(); + IStoreProvider storeProvider = new StoreProvider(dbStoreProvider); + return CheckpointStore.Create(storeProvider); + }) .As() .SingleInstance(); // IMessageStore builder.Register( - c => - { - var checkpointStore = c.Resolve(); - var dbStoreProvider = c.Resolve(); - IStoreProvider storeProvider = new StoreProvider(dbStoreProvider); - IMessageStore messageStore = new MessageStore(storeProvider, checkpointStore, TimeSpan.MaxValue); - return messageStore; - }) - .As() - .SingleInstance(); + c => + { + var checkpointStore = c.Resolve(); + var dbStoreProvider = c.Resolve(); + IStoreProvider storeProvider = new StoreProvider(dbStoreProvider); + IMessageStore messageStore = new MessageStore(storeProvider, checkpointStore, TimeSpan.MaxValue); + return messageStore; + }) + .As() + .SingleInstance(); // IEndpointExecutorFactory builder.Register( - c => - { - var endpointExecutorConfig = c.Resolve(); - var messageStore = c.Resolve(); - IEndpointExecutorFactory endpointExecutorFactory = new StoringAsyncEndpointExecutorFactory(endpointExecutorConfig, new AsyncEndpointExecutorOptions(10, TimeSpan.FromSeconds(10)), messageStore); - return endpointExecutorFactory; - }) - .As() - .SingleInstance(); + c => + { + var endpointExecutorConfig = c.Resolve(); + var messageStore = c.Resolve(); + IEndpointExecutorFactory endpointExecutorFactory = new StoringAsyncEndpointExecutorFactory(endpointExecutorConfig, new AsyncEndpointExecutorOptions(10, TimeSpan.FromSeconds(10)), messageStore); + return endpointExecutorFactory; + }) + .As() + .SingleInstance(); // Task builder.Register( - async c => - { - var checkpointStore = c.Resolve(); - var routerConfig = c.Resolve(); - var endpointExecutorFactory = c.Resolve(); - return await Router.CreateAsync(Guid.NewGuid().ToString(), this.iotHubName, routerConfig, endpointExecutorFactory, checkpointStore); - }) + async c => + { + var checkpointStore = c.Resolve(); + var routerConfig = c.Resolve(); + var endpointExecutorFactory = c.Resolve(); + return await Router.CreateAsync(Guid.NewGuid().ToString(), this.iotHubName, routerConfig, endpointExecutorFactory, checkpointStore); + }) .As>() .SingleInstance(); // Task - builder.Register(async c => - { - var dbStoreProvider = c.Resolve(); - var messageConverterProvider = c.Resolve(); - IConnectionManager connectionManager = await c.Resolve>(); - return TwinManager.CreateTwinManager(connectionManager, messageConverterProvider, Option.Some(new StoreProvider(dbStoreProvider))); - }) + builder.Register( + async c => + { + var dbStoreProvider = c.Resolve(); + var messageConverterProvider = c.Resolve(); + IConnectionManager connectionManager = await c.Resolve>(); + return TwinManager.CreateTwinManager(connectionManager, messageConverterProvider, Option.Some(new StoreProvider(dbStoreProvider))); + }) .As>() .SingleInstance(); } // IClientCredentials "EdgeHubCredentials" builder.Register( - c => - { - var identityFactory = c.Resolve(); - IClientCredentials edgeHubCredentials = this.connectionString.Map(cs => identityFactory.GetWithConnectionString(cs)).GetOrElse( - () => identityFactory.GetWithIotEdged(this.edgeDeviceId, this.edgeModuleId)); - return edgeHubCredentials; - }) + c => + { + var identityFactory = c.Resolve(); + IClientCredentials edgeHubCredentials = this.connectionString.Map(cs => identityFactory.GetWithConnectionString(cs)).GetOrElse( + () => identityFactory.GetWithIotEdged(this.edgeDeviceId, this.edgeModuleId)); + return edgeHubCredentials; + }) .Named("EdgeHubCredentials") .SingleInstance(); // Task - builder.Register(async c => - { - IConnectionManager connectionManager = await c.Resolve>(); - return new InvokeMethodHandler(connectionManager) as IInvokeMethodHandler; - }) + builder.Register( + async c => + { + IConnectionManager connectionManager = await c.Resolve>(); + return new InvokeMethodHandler(connectionManager) as IInvokeMethodHandler; + }) .As>() .SingleInstance(); @@ -399,8 +406,14 @@ protected override void Load(ContainerBuilder builder) ITwinManager twinManager = await twinManagerTask; IConnectionManager connectionManager = await connectionManagerTask; IInvokeMethodHandler invokeMethodHandler = await invokeMethodHandlerTask; - IEdgeHub hub = new RoutingEdgeHub(router, routingMessageConverter, - connectionManager, twinManager, this.edgeDeviceId, invokeMethodHandler, deviceConnectivityManager); + IEdgeHub hub = new RoutingEdgeHub( + router, + routingMessageConverter, + connectionManager, + twinManager, + this.edgeDeviceId, + invokeMethodHandler, + deviceConnectivityManager); return hub; }) .As>() @@ -408,64 +421,63 @@ protected override void Load(ContainerBuilder builder) // Task builder.Register( - async c => - { - IMessageStore messageStore = this.isStoreAndForwardEnabled ? c.Resolve() : null; - Router router = await c.Resolve>(); - var configUpdater = new ConfigUpdater(router, messageStore); - return configUpdater; - }) + async c => + { + IMessageStore messageStore = this.isStoreAndForwardEnabled ? c.Resolve() : null; + Router router = await c.Resolve>(); + var configUpdater = new ConfigUpdater(router, messageStore); + return configUpdater; + }) .As>() .SingleInstance(); // Task builder.Register( - async c => - { - RouteFactory routeFactory = await c.Resolve>(); - if (this.useTwinConfig) - { - var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); - var twinCollectionMessageConverter = c.Resolve>(); - var twinMessageConverter = c.Resolve>(); - var twinManagerTask = c.Resolve>(); - var edgeHubTask = c.Resolve>(); - ITwinManager twinManager = await twinManagerTask; - IEdgeHub edgeHub = await edgeHubTask; - IConnectionManager connectionManager = await c.Resolve>(); - IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache = await c.Resolve>(); - IConfigSource edgeHubConnection = await EdgeHubConnection.Create( - edgeHubCredentials.Identity, - edgeHub, - twinManager, - connectionManager, - routeFactory, - twinCollectionMessageConverter, - twinMessageConverter, - this.versionInfo, - deviceScopeIdentitiesCache - ); - return edgeHubConnection; - } - else + async c => { - return new LocalConfigSource(routeFactory, this.routes, this.storeAndForwardConfiguration); - } - }) + RouteFactory routeFactory = await c.Resolve>(); + if (this.useTwinConfig) + { + var edgeHubCredentials = c.ResolveNamed("EdgeHubCredentials"); + var twinCollectionMessageConverter = c.Resolve>(); + var twinMessageConverter = c.Resolve>(); + var twinManagerTask = c.Resolve>(); + var edgeHubTask = c.Resolve>(); + ITwinManager twinManager = await twinManagerTask; + IEdgeHub edgeHub = await edgeHubTask; + IConnectionManager connectionManager = await c.Resolve>(); + IDeviceScopeIdentitiesCache deviceScopeIdentitiesCache = await c.Resolve>(); + IConfigSource edgeHubConnection = await EdgeHubConnection.Create( + edgeHubCredentials.Identity, + edgeHub, + twinManager, + connectionManager, + routeFactory, + twinCollectionMessageConverter, + twinMessageConverter, + this.versionInfo, + deviceScopeIdentitiesCache); + return edgeHubConnection; + } + else + { + return new LocalConfigSource(routeFactory, this.routes, this.storeAndForwardConfiguration); + } + }) .As>() .SingleInstance(); // Task builder.Register( - async c => - { - var connectionManagerTask = c.Resolve>(); - var edgeHubTask = c.Resolve>(); - IConnectionManager connectionManager = await connectionManagerTask; - IEdgeHub edgeHub = await edgeHubTask; - IConnectionProvider connectionProvider = new ConnectionProvider(connectionManager, edgeHub); - return connectionProvider; - }) + async c => + { + var connectionManagerTask = c.Resolve>(); + var edgeHubTask = c.Resolve>(); + IConnectionManager connectionManager = await connectionManagerTask; + IEdgeHub edgeHub = await edgeHubTask; + IConnectionProvider connectionProvider = new ConnectionProvider(connectionManager, edgeHub); + return connectionProvider; + }) .As>() .SingleInstance(); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Dispatcher.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Dispatcher.cs index 0779b427aec..a67721afe4a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Dispatcher.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Dispatcher.cs @@ -26,14 +26,6 @@ public class Dispatcher : IDisposable readonly string iotHubName; static readonly ICollection EmptyMessages = ImmutableList.Empty; - public string Id { get; } - - public IEnumerable Endpoints => this.Executors.Values.Select(ex => ex.Endpoint); - - public Option Offset => this.checkpointer.Offset > Checkpointer.InvalidOffset ? Option.Some(this.checkpointer.Offset) : Option.None(); - - ImmutableDictionary Executors => this.executors; - Dispatcher(string id, string iotHubName, IEnumerable execs, IEndpointExecutorFactory endpointExecutorFactory, ICheckpointer checkpointer) { this.Id = Preconditions.CheckNotNull(id); @@ -43,11 +35,19 @@ public class Dispatcher : IDisposable this.cts = new CancellationTokenSource(); this.checkpointer = Preconditions.CheckNotNull(checkpointer); - ImmutableDictionary execsDict = Preconditions.CheckNotNull(execs) + ImmutableDictionary execsDict = Preconditions.CheckNotNull(execs) .ToImmutableDictionary(key => key.Endpoint.Id, value => value); this.executors = new AtomicReference>(execsDict); } + public string Id { get; } + + public IEnumerable Endpoints => this.Executors.Values.Select(ex => ex.Endpoint); + + public Option Offset => this.checkpointer.Offset > Checkpointer.InvalidOffset ? Option.Some(this.checkpointer.Offset) : Option.None(); + + ImmutableDictionary Executors => this.executors; + public static async Task CreateAsync(string id, string iotHubName, ISet endpoints, IEndpointExecutorFactory factory) { Preconditions.CheckNotNull(id); @@ -101,20 +101,6 @@ public Task DispatchAsync(IMessage message, ISet endpoints) } } - async Task DispatchInternal(IEndpointExecutor exec, IMessage message) - { - try - { - await exec.Invoke(message); - } - catch (Exception ex) when (ex is InvalidOperationException || ex is OperationCanceledException) - { - // disabled - // Executor is closed, ignore the send - // TODO add logging? - } - } - public async Task SetEndpoint(Endpoint endpoint) { Preconditions.CheckNotNull(endpoint); @@ -140,24 +126,6 @@ public async Task SetEndpoints(IEnumerable endpoints) } } - async Task SetEndpointInternal(Endpoint endpoint) - { - IEndpointExecutor executor; - ImmutableDictionary snapshot = this.executors; - if (!snapshot.TryGetValue(endpoint.Id, out executor)) - { - executor = await this.endpointExecutorFactory.CreateAsync(endpoint); - if (!this.executors.CompareAndSet(snapshot, snapshot.Add(endpoint.Id, executor))) - { - throw new InvalidOperationException($"Invalid set endpoint operation for executor {endpoint.Id}"); - } - } - else - { - await executor.SetEndpoint(endpoint); - } - } - public async Task RemoveEndpoint(string id) { Preconditions.CheckNotNull(id); @@ -183,21 +151,6 @@ public async Task RemoveEndpoints(IEnumerable ids) } } - async Task RemoveEndpointInternal(string id) - { - IEndpointExecutor executor; - ImmutableDictionary snapshot = this.executors; - if (snapshot.TryGetValue(id, out executor)) - { - if (!this.executors.CompareAndSet(snapshot, snapshot.Remove(id))) - { - throw new InvalidOperationException($"Invalid remove endpoint operation for executor {id}"); - } - await executor.CloseAsync(); - executor.Dispose(); - } - } - public async Task ReplaceEndpoints(ISet newEndpoints) { Preconditions.CheckNotNull(newEndpoints); @@ -224,14 +177,6 @@ public async Task ReplaceEndpoints(ISet newEndpoints) } } - void CheckClosed() - { - if (this.closed) - { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} is closed.", this)); - } - } - public async Task CloseAsync(CancellationToken token) { using (await this.sync.LockAsync(CancellationToken.None)) @@ -244,6 +189,7 @@ public async Task CloseAsync(CancellationToken token) { await exec.CloseAsync(); } + await this.checkpointer.CloseAsync(token); } } @@ -251,9 +197,10 @@ public async Task CloseAsync(CancellationToken token) public void Dispose() => this.Dispose(true); + public override string ToString() => string.Format(CultureInfo.InvariantCulture, "Dispatcher({0})", this.Id); + protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.closed); if (disposing) { ImmutableDictionary snapshot = this.executors; @@ -261,13 +208,68 @@ protected virtual void Dispose(bool disposing) { executor.Dispose(); } + this.checkpointer.Dispose(); this.cts.Dispose(); this.sync.Dispose(); } } - public override string ToString() => string.Format(CultureInfo.InvariantCulture, "Dispatcher({0})", this.Id); + async Task DispatchInternal(IEndpointExecutor exec, IMessage message) + { + try + { + await exec.Invoke(message); + } + catch (Exception ex) when (ex is InvalidOperationException || ex is OperationCanceledException) + { + // disabled + // Executor is closed, ignore the send + // TODO add logging? + } + } + + async Task SetEndpointInternal(Endpoint endpoint) + { + IEndpointExecutor executor; + ImmutableDictionary snapshot = this.executors; + if (!snapshot.TryGetValue(endpoint.Id, out executor)) + { + executor = await this.endpointExecutorFactory.CreateAsync(endpoint); + if (!this.executors.CompareAndSet(snapshot, snapshot.Add(endpoint.Id, executor))) + { + throw new InvalidOperationException($"Invalid set endpoint operation for executor {endpoint.Id}"); + } + } + else + { + await executor.SetEndpoint(endpoint); + } + } + + async Task RemoveEndpointInternal(string id) + { + IEndpointExecutor executor; + ImmutableDictionary snapshot = this.executors; + if (snapshot.TryGetValue(id, out executor)) + { + if (!this.executors.CompareAndSet(snapshot, snapshot.Remove(id))) + { + throw new InvalidOperationException($"Invalid remove endpoint operation for executor {id}"); + } + + await executor.CloseAsync(); + executor.Dispose(); + } + } + + void CheckClosed() + { + if (this.closed) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} is closed.", this)); + } + } class CheckpointerEndpointExecutorFactory : IEndpointExecutorFactory { @@ -298,13 +300,13 @@ public Task CreateAsync(Endpoint endpoint, ICheckpointer chec public Task CreateAsync(Endpoint endpoint, ICheckpointer checkpointer, EndpointExecutorConfig endpointExecutorConfig) { return this.executorFactory.CreateAsync(endpoint, checkpointer, endpointExecutorConfig); - } + } } static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.Dispatcher; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { @@ -326,6 +328,5 @@ public static void UnmatchedMessage(string iotHubName, IMessage message) } } } - } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Endpoint.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Endpoint.cs index b46168744de..7e400c94d34 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Endpoint.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Endpoint.cs @@ -6,17 +6,6 @@ namespace Microsoft.Azure.Devices.Routing.Core public abstract class Endpoint : IEquatable { - /// - /// Endpoint identifier. This must be globally unique - /// - public virtual string Id { get; } - - public string Name { get; } - - public string IotHubName { get; } - - public abstract string Type { get; } - protected Endpoint(string id) : this(id, id, string.Empty) { @@ -29,6 +18,17 @@ protected Endpoint(string id, string name, string iotHubName) this.IotHubName = Preconditions.CheckNotNull(iotHubName); } + /// + /// Endpoint identifier. This must be globally unique + /// + public virtual string Id { get; } + + public string Name { get; } + + public string IotHubName { get; } + + public abstract string Type { get; } + public abstract IProcessor CreateProcessor(); public abstract void LogUserMetrics(long messageCount, long latencyInMs); @@ -39,6 +39,7 @@ public bool Equals(Endpoint other) { return false; } + // Name is intentionally left out of equality because it can be updated on the // endpoint without changing the endpoint's functionality return ReferenceEquals(this, other) || string.Equals(this.Id, other.Id); @@ -64,4 +65,4 @@ public override int GetHashCode() return this.Id.GetHashCode(); } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/EndpointHealthData.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/EndpointHealthData.cs index 77e13c23421..1ed289e5bc8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/EndpointHealthData.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/EndpointHealthData.cs @@ -5,14 +5,14 @@ namespace Microsoft.Azure.Devices.Routing.Core public sealed class EndpointHealthData { - public string EndpointId { get; } - - public EndpointHealthStatus HealthStatus { get; } - public EndpointHealthData(string endpointId, EndpointHealthStatus healthStatus) { this.EndpointId = Preconditions.CheckNotNull(endpointId); this.HealthStatus = healthStatus; } + + public string EndpointId { get; } + + public EndpointHealthStatus HealthStatus { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Evaluator.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Evaluator.cs index e0b8027736f..6e0150e3e0a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Evaluator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Evaluator.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Routing.Core using System; using System.Collections.Generic; using System.Collections.Immutable; - using static System.FormattableString; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,28 +12,19 @@ namespace Microsoft.Azure.Devices.Routing.Core using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Extensions.Logging; + using static System.FormattableString; public class Evaluator { static readonly ISet NoEndpoints = ImmutableHashSet.Empty; readonly object sync = new object(); + // ReSharper disable once InconsistentlySynchronizedField - compiledRoutes is immutable readonly AtomicReference> compiledRoutes; readonly IRouteCompiler compiler; readonly Option fallback; - // Because we are only reading here, it doesn't matter that it is under a lock - // ReSharper disable once InconsistentlySynchronizedField - compiledRoutes is immutable - public ISet Routes - { - get - { - ImmutableDictionary snapshot = this.compiledRoutes; - return new HashSet(snapshot.Values.Select(c => c.Route)); - } - } - public Evaluator(RouterConfig config) : this(config, RouteCompiler.Instance) { @@ -52,6 +42,17 @@ public Evaluator(RouterConfig config, IRouteCompiler compiler) this.compiledRoutes = new AtomicReference>(routesDict); } + // Because we are only reading here, it doesn't matter that it is under a lock + // ReSharper disable once InconsistentlySynchronizedField - compiledRoutes is immutable + public ISet Routes + { + get + { + ImmutableDictionary snapshot = this.compiledRoutes; + return new HashSet(snapshot.Values.Select(c => c.Route)); + } + } + public ISet Evaluate(IMessage message) { var endpoints = new HashSet(); @@ -89,26 +90,6 @@ public ISet Evaluate(IMessage message) } } - static bool EvaluateInternal(CompiledRoute compiledRoute, IMessage message) - { - try - { - Bool evaluation = compiledRoute.Evaluate(message); - - if (evaluation.Equals(Bool.Undefined)) - { - Routing.UserAnalyticsLogger.LogUndefinedRouteEvaluation(message, compiledRoute.Route); - } - - return evaluation; - } - catch (Exception ex) - { - Events.EvaluateFailure(compiledRoute.Route, ex); - throw; - } - } - public void SetRoute(Route route) { lock (this.sync) @@ -139,6 +120,26 @@ public void ReplaceRoutes(ISet newRoutes) public Task CloseAsync(CancellationToken token) => TaskEx.Done; + static bool EvaluateInternal(CompiledRoute compiledRoute, IMessage message) + { + try + { + Bool evaluation = compiledRoute.Evaluate(message); + + if (evaluation.Equals(Bool.Undefined)) + { + Routing.UserAnalyticsLogger.LogUndefinedRouteEvaluation(message, compiledRoute.Route); + } + + return evaluation; + } + catch (Exception ex) + { + Events.EvaluateFailure(compiledRoute.Route, ex); + throw; + } + } + CompiledRoute Compile(Route route) { Events.Compile(route); @@ -160,21 +161,21 @@ CompiledRoute Compile(Route route) class CompiledRoute { - public Route Route { get; } - - public Func Evaluate { get; } - public CompiledRoute(Route route, Func evaluate) { this.Route = Preconditions.CheckNotNull(route); this.Evaluate = Preconditions.CheckNotNull(evaluate); } + + public Route Route { get; } + + public Func Evaluate { get; } } static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.Evaluator; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessage.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessage.cs index f089f04a134..a15ccddf406 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessage.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessage.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Devices.Routing.Core using System; using System.Collections.Generic; using Microsoft.Azure.Devices.Routing.Core.MessageSources; - using Microsoft.Azure.Devices.Routing.Core.Query.Types; + using Microsoft.Azure.Devices.Routing.Core.Query.Types; public interface IMessage : IDisposable { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessageStore.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessageStore.cs index 269d9598b4f..1778d8fc196 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessageStore.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/IMessageStore.cs @@ -6,18 +6,18 @@ namespace Microsoft.Azure.Devices.Routing.Core /// /// Provides functionality to store messages - /// Assumes that each message has a SystemProperty EdgeMessageId that + /// Assumes that each message has a SystemProperty EdgeMessageId that /// uniquely identifies a given message - /// Internally implement 2 types of stores (tables) - + /// Internally implement 2 types of stores (tables) - /// 1. A message store containing messages /// 2. A message queue for each endpoint referencing messages in the message store /// public interface IMessageStore : IDisposable { /// - /// Finds message with same edgeMessageId in the message store, + /// Finds message with same edgeMessageId in the message store, /// and if not found, creates one. - /// Creates an entry in the message queue for the given endpoint + /// Creates an entry in the message queue for the given endpoint /// and returns the offset of that entry. /// Task Add(string endpointId, IMessage message); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/InvalidDetails.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/InvalidDetails.cs index e9ff7f84f0c..4027f03f4fb 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/InvalidDetails.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/InvalidDetails.cs @@ -3,14 +3,14 @@ namespace Microsoft.Azure.Devices.Routing.Core { public class InvalidDetails { - public T Item { get; } - - public FailureKind FailureKind { get; } - public InvalidDetails(T item, FailureKind failureKind) { this.Item = item; this.FailureKind = failureKind; } + + public T Item { get; } + + public FailureKind FailureKind { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Message.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Message.cs index 814a1214c49..9bbe9da6b3a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Message.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Message.cs @@ -11,26 +11,12 @@ namespace Microsoft.Azure.Devices.Routing.Core using Microsoft.Azure.Devices.Routing.Core.Query.Types; using Microsoft.Azure.Devices.Routing.Core.Util; using Newtonsoft.Json; - using SystemPropertiesList = Microsoft.Azure.Devices.Routing.Core.SystemProperties; + using SystemPropertiesList = SystemProperties; public class Message : IMessage { readonly Lazy messageQueryProvider; - public IMessageSource MessageSource { get; } - - public byte[] Body { get; } - - public IReadOnlyDictionary Properties { get; } - - public IReadOnlyDictionary SystemProperties { get; } - - public long Offset { get; } - - public DateTime EnqueuedTime { get; } - - public DateTime DequeuedTime { get; } - public Message(IMessageSource messageSource, byte[] body, IDictionary properties) : this(messageSource, body, properties, new Dictionary()) { @@ -76,6 +62,20 @@ public Message(IMessageSource messageSource, byte[] body, IDictionary Properties { get; } + + public IReadOnlyDictionary SystemProperties { get; } + + public long Offset { get; } + + public DateTime EnqueuedTime { get; } + + public DateTime DequeuedTime { get; } + public QueryValue GetQueryValue(string queryString) { return this.messageQueryProvider.Value.GetQueryValue(queryString); @@ -94,12 +94,13 @@ public bool Equals(Message other) } return this.MessageSource.Equals(other.MessageSource) && - this.Offset == other.Offset && - this.Body.SequenceEqual(other.Body) && - this.Properties.Keys.Count() == other.Properties.Keys.Count() && - this.Properties.Keys.All(key => other.Properties.ContainsKey(key) && Equals(this.Properties[key], other.Properties[key]) && - this.SystemProperties.Keys.Count() == other.SystemProperties.Keys.Count() && - this.SystemProperties.Keys.All(skey => other.SystemProperties.ContainsKey(skey) && Equals(this.SystemProperties[skey], other.SystemProperties[skey]))); + this.Offset == other.Offset && + this.Body.SequenceEqual(other.Body) && + this.Properties.Keys.Count() == other.Properties.Keys.Count() && + this.Properties.Keys.All( + key => other.Properties.ContainsKey(key) && Equals(this.Properties[key], other.Properties[key]) && + this.SystemProperties.Keys.Count() == other.SystemProperties.Keys.Count() && + this.SystemProperties.Keys.All(skey => other.SystemProperties.ContainsKey(skey) && Equals(this.SystemProperties[skey], other.SystemProperties[skey]))); } public override bool Equals(object obj) @@ -131,6 +132,15 @@ public void Dispose() GC.SuppressFinalize(this); } + public long Size() + { + long size = 0L; + size += this.Properties.Aggregate(0, (acc, pair) => (acc + pair.Key.Length + pair.Value.Length)); + size += this.SystemProperties.Aggregate(0, (acc, pair) => (acc + pair.Key.Length + pair.Value.Length)); + size += this.Body.Length; + return size; + } + protected virtual void Dispose(bool disposing) { } @@ -160,20 +170,14 @@ Encoding GetMessageEncoding() return encoding; } - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "Content encoding '{0}' is not supported.", encodingPropertyValue)); + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Content encoding '{0}' is not supported.", + encodingPropertyValue)); } throw new InvalidOperationException("Content encoding is not specified in system properties."); } - - public long Size() - { - long size = 0L; - size += this.Properties.Aggregate(0, (acc, pair) => (acc + pair.Key.Length + pair.Value.Length)); - size += this.SystemProperties.Aggregate(0, (acc, pair) => (acc + pair.Key.Length + pair.Value.Length)); - size += this.Body.Length; - return size; - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Microsoft.Azure.Devices.Routing.Core.csproj b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Microsoft.Azure.Devices.Routing.Core.csproj index b1137e2eb9a..d960f42a5fc 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Microsoft.Azure.Devices.Routing.Core.csproj +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Microsoft.Azure.Devices.Routing.Core.csproj @@ -91,4 +91,12 @@ + + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingPerfCounter.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingPerfCounter.cs index 323ff6fd004..ff1499c377c 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingPerfCounter.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingPerfCounter.cs @@ -3,12 +3,12 @@ namespace Microsoft.Azure.Devices.Routing.Core { public class NullRoutingPerfCounter : IRoutingPerfCounter { - public static NullRoutingPerfCounter Instance { get; } = new NullRoutingPerfCounter(); - NullRoutingPerfCounter() { } + public static NullRoutingPerfCounter Instance { get; } = new NullRoutingPerfCounter(); + public bool LogEventProcessingLatency(string iotHubName, string endpointName, string endpointType, string status, long latencyInMs, out string errorString) { errorString = string.Empty; @@ -75,4 +75,4 @@ public bool LogOperationResult(string iotHubName, string operationName, string o return true; } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingUserMetricLogger.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingUserMetricLogger.cs index 49dfe2ab020..596151f7107 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingUserMetricLogger.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullRoutingUserMetricLogger.cs @@ -3,12 +3,12 @@ namespace Microsoft.Azure.Devices.Routing.Core { public class NullRoutingUserMetricLogger : IRoutingUserMetricLogger { - public static NullRoutingUserMetricLogger Instance { get; } = new NullRoutingUserMetricLogger(); - NullRoutingUserMetricLogger() { } + public static NullRoutingUserMetricLogger Instance { get; } = new NullRoutingUserMetricLogger(); + public void LogEgressMetric(long metricValue, string iotHubName, MessageRoutingStatus messageStatus, string messageSource) { } @@ -49,4 +49,4 @@ public void LogBuiltInEndpointLatencyMetric(long metricValue, string iotHubName) { } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullUserAnalyticsLogger.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullUserAnalyticsLogger.cs index e314069f680..a6501306d5e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullUserAnalyticsLogger.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/NullUserAnalyticsLogger.cs @@ -5,42 +5,42 @@ namespace Microsoft.Azure.Devices.Routing.Core public class NullUserAnalyticsLogger : IRoutingUserAnalyticsLogger { - public static NullUserAnalyticsLogger Instance { get; } = new NullUserAnalyticsLogger(); - NullUserAnalyticsLogger() { } + public static NullUserAnalyticsLogger Instance { get; } = new NullUserAnalyticsLogger(); + public void LogOrphanedMessage(string iotHubName, IMessage message) - { + { } public void LogDroppedMessage(string iotHubName, IMessage message, string endpointName, FailureKind failureKind) - { + { } public void LogInvalidMessage(string iotHubName, IMessage message, FailureKind failureKind) - { + { } public void LogUnhealthyEndpoint(string iotHubName, string endpointName, FailureKind failureKind) - { + { } public void LogDeadEndpoint(string iotHubName, string endpointName) - { + { } public void LogHealthyEndpoint(string iotHubName, string endpointName) - { + { } public void LogUndefinedRouteEvaluation(IMessage message, Route route) - { + { } public void LogRouteEvaluationError(IMessage message, Route route, Exception ex) - { + { } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Route.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Route.cs index 6f8632f319b..46fb642fd38 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Route.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Route.cs @@ -11,6 +11,15 @@ namespace Microsoft.Azure.Devices.Routing.Core public class Route : IEquatable { + public Route(string id, string condition, string iotHubName, IMessageSource source, ISet endpoints) + { + this.Id = Preconditions.CheckNotNull(id); + this.Condition = Preconditions.CheckNotNull(condition); + this.IotHubName = Preconditions.CheckNotNull(iotHubName); + this.Source = source; + this.Endpoints = Preconditions.CheckNotNull(endpoints).ToImmutableHashSet(); + } + public string Id { get; } public string Condition { get; } @@ -21,15 +30,6 @@ public class Route : IEquatable public ISet Endpoints { get; } - public Route(string id, string condition, string iotHubName, IMessageSource source, ISet endpoints) - { - this.Id = Preconditions.CheckNotNull(id); - this.Condition = Preconditions.CheckNotNull(condition); - this.IotHubName = Preconditions.CheckNotNull(iotHubName); - this.Source = source; - this.Endpoints = Preconditions.CheckNotNull(endpoints).ToImmutableHashSet(); - } - public bool Equals(Route other) { if (ReferenceEquals(null, other)) @@ -43,9 +43,9 @@ public bool Equals(Route other) } return string.Equals(this.Id, other.Id) && - string.Equals(this.Condition, other.Condition) && - this.Source.Equals(other.Source) && - this.Endpoints.SetEquals(other.Endpoints); + string.Equals(this.Condition, other.Condition) && + this.Source.Equals(other.Source) && + this.Endpoints.SetEquals(other.Endpoints); } public override bool Equals(object obj) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteFactory.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteFactory.cs index dc47fa0df66..18433ee85d3 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteFactory.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteFactory.cs @@ -28,7 +28,7 @@ protected RouteFactory(IEndpointFactory endpointFactory) public Route Create(string routeString) { // Parse route into constituents - this.ParseRoute(Preconditions.CheckNotNull(routeString, nameof(routeString)), out IMessageSource messageSource, out string condition, out Endpoint endpoint); + this.ParseRoute(Preconditions.CheckNotNull(routeString, nameof(routeString)), out IMessageSource messageSource, out string condition, out Endpoint endpoint); var route = new Route(this.GetNextRouteId(), condition, this.IotHubName, messageSource, new HashSet { endpoint }); return route; } @@ -60,10 +60,10 @@ internal void ParseRoute(string routeString, out IMessageSource messageSource, o endpoint = listener.Endpoint; } - private class RouteParserListener : RouteBaseListener + class RouteParserListener : RouteBaseListener { readonly IEndpointFactory endpointFactory; - + public RouteParserListener(IEndpointFactory endpointFactory) { this.endpointFactory = endpointFactory; @@ -98,12 +98,12 @@ public override void ExitSystemEndpoint(RouteParser.SystemEndpointContext contex } public override void ExitFuncEndpoint(RouteParser.FuncEndpointContext context) - { + { string funcName = context.func.Text; - string address = context.endpoint.Text.Trim('"'); + string address = context.endpoint.Text.Trim('"'); Endpoint endpoint = this.endpointFactory.CreateFunctionEndpoint(funcName, address); this.Endpoint = endpoint; } } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteStore.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteStore.cs index cbc45ba3d9e..4ac4b60b7f7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteStore.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RouteStore.cs @@ -27,11 +27,10 @@ public RouteStore(IDictionary configs) } public Task GetRouterConfigAsync(string iotHubName, CancellationToken token) => - Task.FromResult(new RouterConfig( - this.endpoints.GetOrElse(iotHubName, EmptyEndpoints), - this.routes.GetOrElse(iotHubName, EmptyRoutes), - Option.None() - ) - ); + Task.FromResult( + new RouterConfig( + this.endpoints.GetOrElse(iotHubName, EmptyEndpoints), + this.routes.GetOrElse(iotHubName, EmptyRoutes), + Option.None())); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Router.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Router.cs index 7d9d447b856..75134c011ce 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Router.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Router.cs @@ -23,19 +23,6 @@ public class Router : IDisposable readonly AsyncLock sync = new AsyncLock(); readonly string iotHubName; - public string Id { get; } - - public ISet Routes - { - get - { - ImmutableDictionary snapshot = this.routes; - return new HashSet(snapshot.Values); - } - } - - public Option Offset => this.dispatcher.Offset; - Router(string id, string iotHubName, Evaluator evaluator, Dispatcher dispatcher) { this.Id = Preconditions.CheckNotNull(id); @@ -51,6 +38,19 @@ public ISet Routes this.cts = new CancellationTokenSource(); } + public string Id { get; } + + public ISet Routes + { + get + { + ImmutableDictionary snapshot = this.routes; + return new HashSet(snapshot.Values); + } + } + + public Option Offset => this.dispatcher.Offset; + public static async Task CreateAsync(string id, string iotHubName, RouterConfig config, IEndpointExecutorFactory executorFactory) { Preconditions.CheckNotNull(id); @@ -74,13 +74,6 @@ public static async Task CreateAsync(string id, string iotHubName, Route return new Router(id, iotHubName, evaluator, dispatcher); } - static ISet GetEndpoints(RouterConfig config) - { - var endpoints = new HashSet(config.Routes.SelectMany(r => r.Endpoints)); - config.Fallback.ForEach(f => endpoints.UnionWith(f.Endpoints)); - return endpoints; - } - public Task RouteAsync(IMessage message) { this.CheckClosed(); @@ -108,15 +101,6 @@ public async Task RouteAsync(IEnumerable messages) } } - Task RouteInternalAsync(IMessage message) - { - ISet endpoints = this.evaluator.Evaluate(message); - - Events.MessageEvaluation(this.iotHubName, message, endpoints); - - return this.dispatcher.DispatchAsync(message, endpoints); - } - public Option GetRoute(string id) { Route route; @@ -171,14 +155,6 @@ public async Task ReplaceRoutes(ISet newRoutes) } } - void CheckClosed() - { - if (this.closed) - { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Router {0} is closed.", this)); - } - } - public async Task CloseAsync(CancellationToken token) { if (!this.closed.GetAndSet(true)) @@ -194,9 +170,10 @@ public async Task CloseAsync(CancellationToken token) public void Dispose() => this.Dispose(true); + public override string ToString() => string.Format(CultureInfo.InvariantCulture, "Router({0})", this.Id); + protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.closed); if (disposing) { this.cts.Dispose(); @@ -205,12 +182,34 @@ protected virtual void Dispose(bool disposing) } } - public override string ToString() => string.Format(CultureInfo.InvariantCulture, "Router({0})", this.Id); + static ISet GetEndpoints(RouterConfig config) + { + var endpoints = new HashSet(config.Routes.SelectMany(r => r.Endpoints)); + config.Fallback.ForEach(f => endpoints.UnionWith(f.Endpoints)); + return endpoints; + } + + Task RouteInternalAsync(IMessage message) + { + ISet endpoints = this.evaluator.Evaluate(message); + + Events.MessageEvaluation(this.iotHubName, message, endpoints); + + return this.dispatcher.DispatchAsync(message, endpoints); + } + + void CheckClosed() + { + if (this.closed) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Router {0} is closed.", this)); + } + } static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.Router; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { @@ -226,4 +225,4 @@ public static void MessageEvaluation(string iotHubName, IMessage message, ISet Endpoints { get; } - - public Option Fallback { get; } - - public ISet Routes { get; } - public RouterConfig(IEnumerable routes) : this(routes.ToList()) { } - RouterConfig(IList routesList) - : this(routesList.SelectMany(r => r.Endpoints), routesList) - { - } - public RouterConfig(IEnumerable endpoints, IEnumerable routes) : this(endpoints, routes, Option.None()) { @@ -35,5 +24,16 @@ public RouterConfig(IEnumerable endpoints, IEnumerable routes, this.Routes = Preconditions.CheckNotNull(routes).ToImmutableHashSet(); this.Fallback = Preconditions.CheckNotNull(fallback); } + + RouterConfig(IList routesList) + : this(routesList.SelectMany(r => r.Endpoints), routesList) + { + } + + public ISet Endpoints { get; } + + public Option Fallback { get; } + + public ISet Routes { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Routing.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Routing.cs index 624abe34bba..1178018ce6d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Routing.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Routing.cs @@ -5,13 +5,30 @@ namespace Microsoft.Azure.Devices.Routing.Core public static class Routing { + const int EventIdStart = 9000; static IRoutingPerfCounter perfCounter; static IRoutingUserAnalyticsLogger userAnalyticsLogger; static IRoutingUserMetricLogger userMetricLogger; public static ILoggerFactory LoggerFactory { get; set; } = new LoggerFactory(); - - const int EventIdStart = 9000; + + public static IRoutingPerfCounter PerfCounter + { + get => perfCounter ?? NullRoutingPerfCounter.Instance; + set => perfCounter = value; + } + + public static IRoutingUserMetricLogger UserMetricLogger + { + get => userMetricLogger ?? NullRoutingUserMetricLogger.Instance; + set => userMetricLogger = value; + } + + public static IRoutingUserAnalyticsLogger UserAnalyticsLogger + { + get => userAnalyticsLogger ?? NullUserAnalyticsLogger.Instance; + set => userAnalyticsLogger = value; + } public static class EventIds { @@ -37,24 +54,5 @@ public static class EventIds public const int FilteringRoutingService = EventIdStart + 1000; public const int FrontendRoutingService = EventIdStart + 1100; } - - - public static IRoutingPerfCounter PerfCounter - { - get => perfCounter ?? NullRoutingPerfCounter.Instance; - set => perfCounter = value; - } - - public static IRoutingUserMetricLogger UserMetricLogger - { - get => userMetricLogger ?? NullRoutingUserMetricLogger.Instance; - set => userMetricLogger = value; - } - - public static IRoutingUserAnalyticsLogger UserAnalyticsLogger - { - get => userAnalyticsLogger ?? NullUserAnalyticsLogger.Instance; - set => userAnalyticsLogger = value; - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RoutingIdBuilder.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RoutingIdBuilder.cs index 2dad80c70fe..5548c68ec4e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RoutingIdBuilder.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/RoutingIdBuilder.cs @@ -12,16 +12,9 @@ public class RoutingIdBuilder readonly string cachedId; - public string IotHubName { get; } - - public long RouterNumber { get; } - - public Option EndpointId { get; } - public RoutingIdBuilder(string iotHubName, long routerNumber) : this(iotHubName, routerNumber, Option.None()) { - } public RoutingIdBuilder(string iotHubName, long routerNumber, Option endpointId) @@ -40,6 +33,12 @@ public RoutingIdBuilder(string iotHubName, long routerNumber, Option end } } + public string IotHubName { get; } + + public long RouterNumber { get; } + + public Option EndpointId { get; } + public static Option Parse(string id) { string[] tokens = id?.Split(DelimiterArray, StringSplitOptions.RemoveEmptyEntries); @@ -63,6 +62,7 @@ public static Option Parse(string id) { return Option.Some(new RoutingIdBuilder(iotHubName, routerNumber, endpointId)); } + break; } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SendFailureDetails.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SendFailureDetails.cs index 1f9959d4a09..1411aae4262 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SendFailureDetails.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SendFailureDetails.cs @@ -5,14 +5,14 @@ namespace Microsoft.Azure.Devices.Routing.Core public class SendFailureDetails { - public FailureKind FailureKind { get; } - - public Exception RawException { get; } - public SendFailureDetails(FailureKind failureKind, Exception rawException) { this.FailureKind = failureKind; this.RawException = rawException; } + + public FailureKind FailureKind { get; } + + public Exception RawException { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SinkResult.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SinkResult.cs index 2a940c2f0c6..1f58e35c9f6 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SinkResult.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/SinkResult.cs @@ -13,18 +13,6 @@ public class SinkResult : ISinkResult static readonly ICollection EmptyItems = ImmutableList.Empty; static readonly ICollection> EmptyInvalidDetailsList = ImmutableList>.Empty; - public static ISinkResult Empty { get; } = new SinkResult(EmptyItems); - - public ICollection Succeeded { get; } - - public ICollection Failed { get; } - - public ICollection> InvalidDetailsList { get; } - - public Option SendFailureDetails { get; } - - public bool IsSuccessful => !this.Failed.Any(); - public SinkResult(ICollection succeeded) : this(succeeded, EmptyItems, null) { @@ -47,5 +35,17 @@ public SinkResult(ICollection succeeded, ICollection failed, ICollection Empty { get; } = new SinkResult(EmptyItems); + + public ICollection Succeeded { get; } + + public ICollection Failed { get; } + + public ICollection> InvalidDetailsList { get; } + + public Option SendFailureDetails { get; } + + public bool IsSuccessful => !this.Failed.Any(); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Source.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Source.cs index 2b0603892c4..4a9fdc93497 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Source.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/Source.cs @@ -8,15 +8,15 @@ namespace Microsoft.Azure.Devices.Routing.Core public abstract class Source : IDisposable { - public Router Router { get; } - - protected bool Disposed { get; private set; } - protected Source(Router router) { this.Router = Preconditions.CheckNotNull(router); } + public Router Router { get; } + + protected bool Disposed { get; private set; } + public abstract Task RunAsync(); public virtual Task CloseAsync(CancellationToken token) => this.Router.CloseAsync(token); @@ -33,6 +33,7 @@ protected virtual void Dispose(bool disposing) { this.Router.Dispose(); } + this.Disposed = true; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointData.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointData.cs index f61f82e2025..350768ef5f7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointData.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointData.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Checkpointers { - using Microsoft.Azure.Devices.Routing.Core.Util; using System; + using Microsoft.Azure.Devices.Routing.Core.Util; public class CheckpointData { - public CheckpointData(long offset) : this(offset, Option.None(), Option.None()) + public CheckpointData(long offset) + : this(offset, Option.None(), Option.None()) { } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/Checkpointer.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/Checkpointer.cs index 9352b24a6ed..926f11893b5 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/Checkpointer.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/Checkpointer.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Checkpointers using System; using System.Collections.Generic; using System.Diagnostics; - using static System.FormattableString; using System.Globalization; using System.Linq; using System.Threading; @@ -12,6 +11,7 @@ namespace Microsoft.Azure.Devices.Routing.Core.Checkpointers using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Extensions.Logging; + using static System.FormattableString; public class Checkpointer : ICheckpointer { @@ -21,6 +21,17 @@ public class Checkpointer : ICheckpointer readonly AtomicBoolean closed; readonly ICheckpointStore store; + Checkpointer(string id, ICheckpointStore store, CheckpointData checkpointData) + { + this.Id = Preconditions.CheckNotNull(id); + this.store = Preconditions.CheckNotNull(store); + this.Offset = checkpointData.Offset; + this.LastFailedRevivalTime = checkpointData.LastFailedRevivalTime; + this.UnhealthySince = checkpointData.UnhealthySince; + this.Proposed = checkpointData.Offset; + this.closed = new AtomicBoolean(false); + } + public string Id { get; } public long Offset { get; private set; } @@ -33,17 +44,6 @@ public class Checkpointer : ICheckpointer public bool HasOutstanding => this.Offset < this.Proposed; - Checkpointer(string id, ICheckpointStore store, CheckpointData checkpointData) - { - this.Id = Preconditions.CheckNotNull(id); - this.store = Preconditions.CheckNotNull(store); - this.Offset = checkpointData.Offset; - this.LastFailedRevivalTime = checkpointData.LastFailedRevivalTime; - this.UnhealthySince = checkpointData.UnhealthySince; - this.Proposed = checkpointData.Offset; - this.closed = new AtomicBoolean(false); - } - public static async Task CreateAsync(string id, ICheckpointStore store) { Preconditions.CheckNotNull(id); @@ -141,8 +141,8 @@ void CheckClosed() static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.Checkpointer; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointerStatus.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointerStatus.cs index 18f4032604d..87086077bc5 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointerStatus.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/CheckpointerStatus.cs @@ -3,17 +3,17 @@ namespace Microsoft.Azure.Devices.Routing.Core.Checkpointers { public class CheckpointerStatus { - public string Id { get; } - - public long Offset { get; } - - public long Proposed { get; } - public CheckpointerStatus(string id, long offset, long proposed) { this.Id = id; this.Offset = offset; this.Proposed = proposed; } + + public string Id { get; } + + public long Offset { get; } + + public long Proposed { get; } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/InMemoryCheckpointStore.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/InMemoryCheckpointStore.cs index 5dbe7a97ea6..d09b294ad4c 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/InMemoryCheckpointStore.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/InMemoryCheckpointStore.cs @@ -38,7 +38,7 @@ public Task GetCheckpointDataAsync(string id, CancellationToken public Task> GetAllCheckpointDataAsync(CancellationToken token) { - return Task.FromResult((IDictionary )this.checkpointDataMap.ToDictionary(keySelector => keySelector.Key, valueSelector => valueSelector.Value)); + return Task.FromResult((IDictionary)this.checkpointDataMap.ToDictionary(keySelector => keySelector.Key, valueSelector => valueSelector.Value)); } public Task SetCheckpointDataAsync(string id, CheckpointData checkpointData, CancellationToken token) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/MasterCheckpointer.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/MasterCheckpointer.cs index 7063e4a1969..2fb9f625091 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/MasterCheckpointer.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/MasterCheckpointer.cs @@ -5,14 +5,13 @@ namespace Microsoft.Azure.Devices.Routing.Core.Checkpointers using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; - using static System.FormattableString; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Extensions.Logging; - using AsyncLock = Microsoft.Azure.Devices.Routing.Core.Util.Concurrency.AsyncLock; + using static System.FormattableString; public class MasterCheckpointer : ICheckpointer, ICheckpointerFactory { @@ -24,6 +23,17 @@ public class MasterCheckpointer : ICheckpointer, ICheckpointerFactory readonly AtomicReference> childCheckpointers; readonly AsyncLock sync; + MasterCheckpointer(string id, ICheckpointStore store, long offset) + { + this.Id = Preconditions.CheckNotNull(id); + this.store = Preconditions.CheckNotNull(store); + this.Offset = offset; + this.Proposed = offset; + this.closed = new AtomicBoolean(false); + this.childCheckpointers = new AtomicReference>(ImmutableDictionary.Empty); + this.sync = new AsyncLock(); + } + public string Id { get; } public long Offset { get; private set; } @@ -38,17 +48,6 @@ public class MasterCheckpointer : ICheckpointer, ICheckpointerFactory ImmutableDictionary ChildCheckpointers => this.childCheckpointers; - MasterCheckpointer(string id, ICheckpointStore store, long offset) - { - this.Id = Preconditions.CheckNotNull(id); - this.store = Preconditions.CheckNotNull(store); - this.Offset = offset; - this.Proposed = offset; - this.closed = new AtomicBoolean(false); - this.childCheckpointers = new AtomicReference>(ImmutableDictionary.Empty); - this.sync = new AsyncLock(); - } - public static async Task CreateAsync(string id, ICheckpointStore store) { Preconditions.CheckNotNull(id); @@ -98,6 +97,36 @@ public async Task CommitAsync(ICollection successful, ICollection this.Dispose(true); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.sync.Dispose(); + } + } + async Task CommitInternalAsync(ICollection successful, ICollection remaining, CancellationToken token) { long offset; @@ -175,37 +204,8 @@ long OffsetFromMessages(ICollection successful, ICollection .Where(m => m.Offset < minOffsetRemaining) .Aggregate(this.Offset, (acc, m) => Math.Max(acc, m.Offset)); } - return offset; - } - public async Task CloseAsync(CancellationToken token) - { - if (!this.closed.GetAndSet(true)) - { - // Store the cached offset on closed to handle case where stores to the store are - // throttled, but a clean shutdown is needed - try - { - if (this.Offset != Checkpointer.InvalidOffset) - { - await this.store.SetCheckpointDataAsync(this.Id, new CheckpointData(this.Offset), CancellationToken.None); - Events.Close(this); - } - } - catch (TaskCanceledException) - { - } - } - } - - public void Dispose() => this.Dispose(true); - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.sync.Dispose(); - } + return offset; } async Task AddChild(ICheckpointer checkpointer) @@ -289,8 +289,8 @@ public async Task CloseAsync(CancellationToken token) static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.MasterCheckpointer; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/NullCheckpointStore.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/NullCheckpointStore.cs index 7189943b5d1..cbad4313e19 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/NullCheckpointStore.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/checkpointers/NullCheckpointStore.cs @@ -12,8 +12,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Checkpointers /// public class NullCheckpointStore : ICheckpointStore { - public static NullCheckpointStore Instance { get; } = new NullCheckpointStore(); - readonly Task initialCheckpointData; readonly Task> initialCheckpointDataMap; @@ -26,12 +24,16 @@ public NullCheckpointStore(long offset) { var data = new CheckpointData(offset); this.initialCheckpointData = Task.FromResult(data); - this.initialCheckpointDataMap = Task.FromResult(new Dictionary() - { - { "NullCheckpoint", data } - } as IDictionary); + this.initialCheckpointDataMap = Task.FromResult( + new Dictionary() + { + { "NullCheckpoint", data } + } + as IDictionary); } + public static NullCheckpointStore Instance { get; } = new NullCheckpointStore(); + public Task GetCheckpointDataAsync(string id, CancellationToken token) { return this.initialCheckpointData; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutor.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutor.cs index c7dfae9e176..b4bdf65137d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutor.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutor.cs @@ -30,7 +30,7 @@ public class AsyncEndpointExecutor : IEndpointExecutor { const int MaxMessagesPerTask = 1000; - readonly Timer batchTimer; + readonly Timer batchTimer; readonly AtomicBoolean closed; readonly CancellationTokenSource cts; readonly ITargetBlock head; @@ -39,10 +39,6 @@ public class AsyncEndpointExecutor : IEndpointExecutor readonly IDataflowBlock tail; readonly ICheckpointer checkpointer; - public Endpoint Endpoint => this.machine.Endpoint; - - public EndpointExecutorStatus Status => this.machine.Status; - public AsyncEndpointExecutor(Endpoint endpoint, ICheckpointer checkpointer, EndpointExecutorConfig config, AsyncEndpointExecutorOptions options) { Preconditions.CheckNotNull(endpoint); @@ -77,37 +73,12 @@ public AsyncEndpointExecutor(Endpoint endpoint, ICheckpointer checkpointer, Endp this.head = batchBlock; this.tail = process; } - - public virtual Task Invoke(IMessage message) => this.SendToTplHead(message); - async Task SendToTplHead(IMessage message) - { - try - { - Preconditions.CheckNotNull(message); + public Endpoint Endpoint => this.machine.Endpoint; - this.checkpointer.Propose(message); + public EndpointExecutorStatus Status => this.machine.Status; - if (this.closed) - { - throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed."); - } - await this.head.SendAsync(message, this.cts.Token); - Events.InvokeSuccess(this, message); - } - catch (Exception ex) - { - if (this.closed) - { - Events.InvokeWarning(this, ex, message); - } - else - { - Events.InvokeFailure(this, ex, message); - throw; - } - } - } + public virtual Task Invoke(IMessage message) => this.SendToTplHead(message); public async Task SetEndpoint(Endpoint newEndpoint) { @@ -122,6 +93,7 @@ public async Task SetEndpoint(Endpoint newEndpoint) { throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed."); } + await this.machine.RunAsync(Commands.UpdateEndpoint(newEndpoint)); Events.SetEndpointSuccess(this); } @@ -144,6 +116,7 @@ public virtual async Task CloseAsync() this.head.Complete(); await Task.WhenAll(this.tail.Completion, this.machine.RunAsync(Commands.Close)); } + Events.CloseSuccess(this); } catch (Exception ex) @@ -157,7 +130,6 @@ public virtual async Task CloseAsync() protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.closed); if (disposing) { this.batchTimer.Dispose(); @@ -166,6 +138,48 @@ protected virtual void Dispose(bool disposing) } } + /// + /// Batch trigger timer callback. This callback is disabled when processing a group of messages + /// and re-enabled after processing for the batch has completed. It is harmless if TriggerBatch + /// is called multiple times (in the worst case several extra batches will be generated, but they + /// will all be processed correctly). + /// + /// + static void Trigger(object block) + { + ((BatchBlock)block).TriggerBatch(); + } + + async Task SendToTplHead(IMessage message) + { + try + { + Preconditions.CheckNotNull(message); + + this.checkpointer.Propose(message); + + if (this.closed) + { + throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed."); + } + + await this.head.SendAsync(message, this.cts.Token); + Events.InvokeSuccess(this, message); + } + catch (Exception ex) + { + if (this.closed) + { + Events.InvokeWarning(this, ex, message); + } + else + { + Events.InvokeFailure(this, ex, message); + throw; + } + } + } + async Task MessagesAction(IMessage[] messages) { try @@ -182,6 +196,7 @@ async Task MessagesAction(IMessage[] messages) { this.batchTimer.Change(this.options.BatchTimeout, this.options.BatchTimeout); } + Events.ProcessMessagesSuccess(this, messages); } catch (Exception ex) @@ -191,22 +206,10 @@ async Task MessagesAction(IMessage[] messages) } } - /// - /// Batch trigger timer callback. This callback is disabled when processing a group of messages - /// and re-enabled after processing for the batch has completed. It is harmless if TriggerBatch - /// is called multiple times (in the worst case several extra batches will be generated, but they - /// will all be processed correctly). - /// - /// - static void Trigger(object block) - { - ((BatchBlock)block).TriggerBatch(); - } - static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.AsyncEndpointExecutor; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { @@ -236,8 +239,13 @@ public static void InvokeFailure(AsyncEndpointExecutor executor, Exception ex, I public static void InvokeWarning(AsyncEndpointExecutor executor, Exception ex, IMessage message) { - Log.LogWarning((int)EventIds.InvokeWarning, ex, "[InvokeWarning] Invoke failed. EndpointId: {0}, EndpointName: {1}, Offset: {2}", - executor.Endpoint.Id, executor.Endpoint.Name, message?.Offset); + Log.LogWarning( + (int)EventIds.InvokeWarning, + ex, + "[InvokeWarning] Invoke failed. EndpointId: {0}, EndpointName: {1}, Offset: {2}", + executor.Endpoint.Id, + executor.Endpoint.Name, + message?.Offset); } public static void ProcessMessages(AsyncEndpointExecutor executor, ICollection messages) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorFactory.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorFactory.cs index bc32b6b30de..d28166709f9 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorFactory.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorFactory.cs @@ -32,6 +32,6 @@ public Task CreateAsync(Endpoint endpoint, ICheckpointer chec { IEndpointExecutor executor = new AsyncEndpointExecutor(endpoint, checkpointer, endpointExecutorConfig, this.options); return Task.FromResult(executor); - } + } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorOptions.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorOptions.cs index aa55b0d4d81..feae4ac33e1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorOptions.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/AsyncEndpointExecutorOptions.cs @@ -8,10 +8,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints public class AsyncEndpointExecutorOptions { - public int BatchSize { get; } - - public TimeSpan BatchTimeout { get; } - public AsyncEndpointExecutorOptions(int batchSize) : this(batchSize, Timeout.InfiniteTimeSpan) { @@ -23,5 +19,9 @@ public AsyncEndpointExecutorOptions(int batchSize, TimeSpan batchTimeout) this.BatchSize = batchSize; this.BatchTimeout = batchTimeout; } + + public int BatchSize { get; } + + public TimeSpan BatchTimeout { get; } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/ConsoleEndpoint.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/ConsoleEndpoint.cs index 8190fdacdf6..f24d229a4d8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/ConsoleEndpoint.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/ConsoleEndpoint.cs @@ -44,16 +44,16 @@ class Processor : IProcessor readonly ConsoleEndpoint endpoint; int count; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); - public Processor(ConsoleEndpoint endpoint) { this.endpoint = Preconditions.CheckNotNull(endpoint); this.count = 0; } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); @@ -66,8 +66,10 @@ public Task> ProcessAsync(ICollection messages, { Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "({0}) {1}: {2}", this.count, this.endpoint, message)); } + this.count++; } + Console.ResetColor(); ISinkResult result = new SinkResult(messages); return Task.FromResult(result); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorConfig.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorConfig.cs index e133feca450..dc757167215 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorConfig.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorConfig.cs @@ -6,14 +6,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints public class EndpointExecutorConfig { - public TimeSpan Timeout { get; } - - public TimeSpan RevivePeriod { get; } - - public RetryStrategy RetryStrategy { get; } - - public bool ThrowOnDead { get; } - public EndpointExecutorConfig(TimeSpan timeout, RetryStrategy retryStrategy, TimeSpan revivePeriod) : this(timeout, retryStrategy, revivePeriod, false) { @@ -33,5 +25,13 @@ public EndpointExecutorConfig(TimeSpan timeout, RetryStrategy retryStrategy, Tim this.RevivePeriod = revivePeriod; this.ThrowOnDead = throwOnDead; } + + public TimeSpan Timeout { get; } + + public TimeSpan RevivePeriod { get; } + + public RetryStrategy RetryStrategy { get; } + + public bool ThrowOnDead { get; } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorStatus.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorStatus.cs index 3ac59bbe915..0f598312fb2 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorStatus.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/EndpointExecutorStatus.cs @@ -3,11 +3,22 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints { using System; using Microsoft.Azure.Devices.Routing.Core.Checkpointers; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine; + using Microsoft.Azure.Devices.Routing.Core.Util; public class EndpointExecutorStatus { + public EndpointExecutorStatus(string id, State state, int retryAttempts, TimeSpan retryPeriod, Option lastFailedRevivalTime, Option unhealthySince, CheckpointerStatus checkpointerStatus) + { + this.Id = id; + this.State = state; + this.RetryAttempts = retryAttempts; + this.RetryPeriod = retryPeriod; + this.LastFailedRevivalTime = lastFailedRevivalTime; + this.UnhealthySince = unhealthySince; + this.CheckpointerStatus = checkpointerStatus; + } + public string Id { get; } public State State { get; } @@ -21,16 +32,5 @@ public class EndpointExecutorStatus public Option UnhealthySince { get; } public CheckpointerStatus CheckpointerStatus { get; } - - public EndpointExecutorStatus(string id, State state, int retryAttempts, TimeSpan retryPeriod, Option lastFailedRevivalTime, Option unhealthySince, CheckpointerStatus checkpointerStatus) - { - this.Id = id; - this.State = state; - this.RetryAttempts = retryAttempts; - this.RetryPeriod = retryPeriod; - this.LastFailedRevivalTime = lastFailedRevivalTime; - this.UnhealthySince = unhealthySince; - this.CheckpointerStatus = checkpointerStatus; - } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/NullEndpoint.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/NullEndpoint.cs index 5430601d45b..241120c9d21 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/NullEndpoint.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/NullEndpoint.cs @@ -34,15 +34,15 @@ class Processor : IProcessor { readonly NullEndpoint endpoint; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); - public Processor(NullEndpoint endpoint) { this.endpoint = Preconditions.CheckNotNull(endpoint); } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutor.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutor.cs index 5e5a5771488..9b38572b8f7 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutor.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutor.cs @@ -26,11 +26,8 @@ public class StoringAsyncEndpointExecutor : IEndpointExecutor readonly EndpointExecutorFsm machine; readonly CancellationTokenSource cts = new CancellationTokenSource(); - public Endpoint Endpoint => this.machine.Endpoint; - - public EndpointExecutorStatus Status => this.machine.Status; - - public StoringAsyncEndpointExecutor(Endpoint endpoint, + public StoringAsyncEndpointExecutor( + Endpoint endpoint, ICheckpointer checkpointer, EndpointExecutorConfig config, AsyncEndpointExecutorOptions options, @@ -45,6 +42,10 @@ public StoringAsyncEndpointExecutor(Endpoint endpoint, this.sendMessageTask = Task.Run(this.SendMessagesPump); } + public Endpoint Endpoint => this.machine.Endpoint; + + public EndpointExecutorStatus Status => this.machine.Status; + public async Task Invoke(IMessage message) { try @@ -53,12 +54,14 @@ public async Task Invoke(IMessage message) { throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed."); } + using (Metrics.StoreLatency(this.Endpoint.Id)) { long offset = await this.messageStore.Add(this.Endpoint.Id, message); this.checkpointer.Propose(message); Events.AddMessageSuccess(this, offset); } + this.hasMessagesInQueue.Set(); Metrics.StoredCountIncrement(this.Endpoint.Id); } @@ -69,60 +72,8 @@ public async Task Invoke(IMessage message) } } - async Task SendMessagesPump() - { - try - { - Events.StartSendMessagesPump(this); - IMessageIterator iterator = this.messageStore.GetMessageIterator(this.Endpoint.Id, this.checkpointer.Offset + 1); - while (!this.cts.IsCancellationRequested) - { - try - { - this.hasMessagesInQueue.WaitOne(this.options.BatchTimeout); - IMessage[] messages = (await iterator.GetNext(this.options.BatchSize)).ToArray(); - await this.ProcessMessages(messages); - Events.SendMessagesSuccess(this, messages); - Metrics.DrainedCountIncrement(this.Endpoint.Id, messages.Length); - - // If store has no messages, then reset the hasMessagesInQueue flag. - if (messages.Length == 0) - { - this.hasMessagesInQueue.Reset(); - } - } - catch (Exception ex) - { - Events.SendMessagesError(this, ex); - // Swallow exception and keep trying. - } - } - } - catch (Exception ex) - { - Events.SendMessagesPumpFailure(this, ex); - } - } - - async Task ProcessMessages(IMessage[] messages) - { - Events.ProcessingMessages(this, messages); - SendMessage command = Commands.SendMessage(messages); - await this.machine.RunAsync(command); - await command.Completion; - } - public void Dispose() => this.Dispose(true); - void Dispose(bool disposing) - { - if (disposing) - { - this.cts.Dispose(); - this.machine.Dispose(); - } - } - public async Task CloseAsync() { Events.Close(this); @@ -135,6 +86,7 @@ public async Task CloseAsync() await (this.messageStore?.RemoveEndpoint(this.Endpoint.Id) ?? Task.CompletedTask); await (this.sendMessageTask ?? Task.CompletedTask); } + Events.CloseSuccess(this); } catch (Exception ex) @@ -157,6 +109,7 @@ public async Task SetEndpoint(Endpoint newEndpoint) { throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed."); } + await this.machine.RunAsync(Commands.UpdateEndpoint(newEndpoint)); Events.SetEndpointSuccess(this); } @@ -167,42 +120,62 @@ public async Task SetEndpoint(Endpoint newEndpoint) } } - static class Metrics + async Task SendMessagesPump() { - static readonly CounterOptions EndpointMessageStoredCountOptions = new CounterOptions - { - Name = "EndpointMessageStoredCount", - MeasurementUnit = Unit.Events - }; - static readonly CounterOptions EndpointMessageDrainedCountOptions = new CounterOptions - { - Name = "EndpointMessageDrainedCount", - MeasurementUnit = Unit.Events - }; - static readonly TimerOptions EndpointMessageLatencyOptions = new TimerOptions + try { - Name = "EndpointMessageStoredLatencyMs", - MeasurementUnit = Unit.None, - DurationUnit = TimeUnit.Milliseconds, - RateUnit = TimeUnit.Seconds - }; + Events.StartSendMessagesPump(this); + IMessageIterator iterator = this.messageStore.GetMessageIterator(this.Endpoint.Id, this.checkpointer.Offset + 1); + while (!this.cts.IsCancellationRequested) + { + try + { + this.hasMessagesInQueue.WaitOne(this.options.BatchTimeout); + IMessage[] messages = (await iterator.GetNext(this.options.BatchSize)).ToArray(); + await this.ProcessMessages(messages); + Events.SendMessagesSuccess(this, messages); + Metrics.DrainedCountIncrement(this.Endpoint.Id, messages.Length); - internal static MetricTags GetTags(string id) + // If store has no messages, then reset the hasMessagesInQueue flag. + if (messages.Length == 0) + { + this.hasMessagesInQueue.Reset(); + } + } + catch (Exception ex) + { + Events.SendMessagesError(this, ex); + // Swallow exception and keep trying. + } + } + } + catch (Exception ex) { - return new MetricTags("EndpointId", id); + Events.SendMessagesPumpFailure(this, ex); } + } - public static void StoredCountIncrement(string identity) => Edge.Util.Metrics.CountIncrement(GetTags(identity), EndpointMessageStoredCountOptions, 1); - - public static void DrainedCountIncrement(string identity, long amount) => Edge.Util.Metrics.CountIncrement(GetTags(identity), EndpointMessageDrainedCountOptions, amount); + async Task ProcessMessages(IMessage[] messages) + { + Events.ProcessingMessages(this, messages); + SendMessage command = Commands.SendMessage(messages); + await this.machine.RunAsync(command); + await command.Completion; + } - public static IDisposable StoreLatency(string identity) => Edge.Util.Metrics.Latency(GetTags(identity), EndpointMessageLatencyOptions); + void Dispose(bool disposing) + { + if (disposing) + { + this.cts.Dispose(); + this.machine.Dispose(); + } } static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.StoringAsyncEndpointExecutor; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { @@ -291,5 +264,39 @@ public static void CloseFailure(StoringAsyncEndpointExecutor executor, Exception Log.LogError((int)EventIds.CloseFailure, ex, "[CloseFailure] Close failed. EndpointId: {0}, EndpointName: {1}", executor.Endpoint.Id, executor.Endpoint.Name); } } + + static class Metrics + { + static readonly CounterOptions EndpointMessageStoredCountOptions = new CounterOptions + { + Name = "EndpointMessageStoredCount", + MeasurementUnit = Unit.Events + }; + + static readonly CounterOptions EndpointMessageDrainedCountOptions = new CounterOptions + { + Name = "EndpointMessageDrainedCount", + MeasurementUnit = Unit.Events + }; + + static readonly TimerOptions EndpointMessageLatencyOptions = new TimerOptions + { + Name = "EndpointMessageStoredLatencyMs", + MeasurementUnit = Unit.None, + DurationUnit = TimeUnit.Milliseconds, + RateUnit = TimeUnit.Seconds + }; + + public static void StoredCountIncrement(string identity) => Edge.Util.Metrics.CountIncrement(GetTags(identity), EndpointMessageStoredCountOptions, 1); + + public static void DrainedCountIncrement(string identity, long amount) => Edge.Util.Metrics.CountIncrement(GetTags(identity), EndpointMessageDrainedCountOptions, amount); + + public static IDisposable StoreLatency(string identity) => Edge.Util.Metrics.Latency(GetTags(identity), EndpointMessageLatencyOptions); + + internal static MetricTags GetTags(string id) + { + return new MetricTags("EndpointId", id); + } + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutorFactory.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutorFactory.cs index 30ba0a77c9b..2115aeba1dd 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutorFactory.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/StoringAsyncEndpointExecutorFactory.cs @@ -32,5 +32,5 @@ public async Task CreateAsync(Endpoint endpoint, ICheckpointe IEndpointExecutor endpointExecutor = new StoringAsyncEndpointExecutor(endpoint, checkpointer, endpointExecutorConfig, this.options, this.messageStore); return endpointExecutor; } - } + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutor.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutor.cs index 182e1a4ea61..3e6cbd22456 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutor.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutor.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints { using System; - using static System.FormattableString; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; @@ -10,6 +9,7 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Extensions.Logging; + using static System.FormattableString; public class SyncEndpointExecutor : IEndpointExecutor { @@ -24,10 +24,6 @@ public class SyncEndpointExecutor : IEndpointExecutor readonly EndpointExecutorFsm machine; readonly AsyncLock sync; - public Endpoint Endpoint => this.machine.Endpoint; - - public EndpointExecutorStatus Status => this.machine.Status; - public SyncEndpointExecutor(Endpoint endpoint, ICheckpointer checkpointer) : this(endpoint, checkpointer, DefaultConfig) { @@ -45,6 +41,10 @@ public SyncEndpointExecutor(Endpoint endpoint, ICheckpointer checkpointer, Endpo this.sync = new AsyncLock(); } + public Endpoint Endpoint => this.machine.Endpoint; + + public EndpointExecutorStatus Status => this.machine.Status; + public async Task Invoke(IMessage message) { Events.Invoke(this); @@ -66,6 +66,7 @@ public async Task Invoke(IMessage message) await this.machine.RunAsync(command); await command.Completion; } + Events.InvokeSuccess(this); } catch (OperationCanceledException) when (this.cts.IsCancellationRequested) @@ -114,6 +115,7 @@ public async Task CloseAsync() this.cts.Cancel(); await this.machine.RunAsync(Commands.Close); } + Events.CloseSuccess(this); } catch (Exception ex) @@ -127,7 +129,6 @@ public async Task CloseAsync() protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.closed); if (disposing) { this.cts.Dispose(); @@ -138,8 +139,8 @@ protected virtual void Dispose(bool disposing) static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.SyncEndpointExecutor; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutorFactory.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutorFactory.cs index 5933c24a241..953124457a0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutorFactory.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/SyncEndpointExecutorFactory.cs @@ -32,6 +32,6 @@ public Task CreateAsync(Endpoint endpoint, ICheckpointer chec { IEndpointExecutor executor = new SyncEndpointExecutor(endpoint, checkpointer, endpointExecutorConfig); return Task.FromResult(executor); - } + } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/EndpointExecutorFsm.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/EndpointExecutorFsm.cs index 47800bade2d..fcb1a552743 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/EndpointExecutorFsm.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/EndpointExecutorFsm.cs @@ -12,8 +12,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Util; + using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Extensions.Logging; - using AsyncLock = Microsoft.Azure.Devices.Routing.Core.Util.Concurrency.AsyncLock; public class EndpointExecutorFsm : IDisposable { @@ -22,25 +22,73 @@ public class EndpointExecutorFsm : IDisposable static readonly TimeSpan LogUserAnalyticsErrorOnUnhealthySince = TimeSpan.FromMinutes(10); static readonly ICollection EmptyMessages = ImmutableList.Empty; + static readonly IReadOnlyDictionary Actions = new Dictionary + { + { State.Idle, new StateActions(EnterIdleAsync, StateActions.NullAction) }, + { State.Sending, new StateActions(EnterSendingAsync, StateActions.NullAction) }, + { State.Checkpointing, new StateActions(EnterCheckpointingAsync, StateActions.NullAction) }, + { State.Failing, new StateActions(EnterFailingAsync, ExitFailingAsync) }, + { State.DeadCheckpointing, new StateActions(EnterDeadCheckpointingAsync, ExitDeadCheckpointingAsync) }, + { State.DeadIdle, new StateActions(StateActions.NullAction, StateActions.NullAction) }, + { State.DeadProcess, new StateActions(EnterProcessDeadAsync, StateActions.NullAction) }, + { State.Closed, new StateActions(EnterClosedAsync, StateActions.NullAction) } + }; + + static readonly IReadOnlyDictionary Transitions = new Dictionary + { + // Idle + { new StateCommandPair(State.Idle, CommandType.SendMessage), new StateTransition(State.Sending, PrepareForSendAsync) }, + { new StateCommandPair(State.Idle, CommandType.UpdateEndpoint), new StateTransition(State.Idle, UpdateEndpointAsync) }, + { new StateCommandPair(State.Idle, CommandType.Close), new StateTransition(State.Closed) }, + + // Sending + { new StateCommandPair(State.Sending, CommandType.Checkpoint), new StateTransition(State.Checkpointing, PrepareForCheckpointAsync) }, + { new StateCommandPair(State.Sending, CommandType.Throw), new StateTransition(State.Idle, ThrowCompleteAsync) }, + { new StateCommandPair(State.Sending, CommandType.Fail), new StateTransition(State.Failing, FailAsync) }, + { new StateCommandPair(State.Sending, CommandType.Die), new StateTransition(State.DeadProcess, DieAsync) }, + + // Checkpointing + { new StateCommandPair(State.Checkpointing, CommandType.Succeed), new StateTransition(State.Idle, SucceedCompleteAsync) }, + { new StateCommandPair(State.Checkpointing, CommandType.Fail), new StateTransition(State.Failing, FailAsync) }, + { new StateCommandPair(State.Checkpointing, CommandType.Die), new StateTransition(State.DeadProcess, DieAsync) }, + { new StateCommandPair(State.Checkpointing, CommandType.Throw), new StateTransition(State.Idle, ThrowCompleteAsync) }, + { new StateCommandPair(State.Checkpointing, CommandType.SendMessage), new StateTransition(State.Sending, PrepareForSendAsync) }, + + // Failing + { new StateCommandPair(State.Failing, CommandType.Retry), new StateTransition(State.Sending) }, + { new StateCommandPair(State.Failing, CommandType.UpdateEndpoint), new StateTransition(State.Sending, UpdateEndpointAsync) }, + { new StateCommandPair(State.Failing, CommandType.Die), new StateTransition(State.DeadProcess, DieAsync) }, + { new StateCommandPair(State.Failing, CommandType.Close), new StateTransition(State.Closed) }, + + // Idle Dead + { new StateCommandPair(State.DeadIdle, CommandType.SendMessage), new StateTransition(State.DeadProcess, PrepareForSendAsync) }, + { new StateCommandPair(State.DeadIdle, CommandType.UpdateEndpoint), new StateTransition(State.Idle, UpdateEndpointAsync) }, + { new StateCommandPair(State.DeadIdle, CommandType.Close), new StateTransition(State.Closed) }, + + // ProcessDead + { new StateCommandPair(State.DeadProcess, CommandType.Revive), new StateTransition(State.Sending, PrepareForReviveAsync) }, + { new StateCommandPair(State.DeadProcess, CommandType.Checkpoint), new StateTransition(State.DeadCheckpointing, PrepareForCheckpointAsync) }, + + // DeadCheckpointing + { new StateCommandPair(State.DeadCheckpointing, CommandType.DeadSucceed), new StateTransition(State.DeadIdle) }, + + // Closed + { new StateCommandPair(State.Closed, CommandType.SendMessage), new StateTransition(State.Closed, SendMessageClosedAsync) }, + }; + + readonly EndpointExecutorConfig config; + readonly Timer retryTimer; + readonly AsyncLock sync = new AsyncLock(); + readonly ISystemTime systemTime; + volatile State state; volatile int retryAttempts; - readonly EndpointExecutorConfig config; SendMessage currentSendCommand; Checkpoint currentCheckpointCommand; Option lastFailedRevivalTime; Option unhealthySince; volatile IProcessor processor; TimeSpan retryPeriod; - readonly Timer retryTimer; - readonly AsyncLock sync = new AsyncLock(); - readonly ISystemTime systemTime; - - public Endpoint Endpoint => this.processor.Endpoint; - - public ICheckpointer Checkpointer { get; } - - public EndpointExecutorStatus Status => - new EndpointExecutorStatus(this.Endpoint.Id, this.state, this.retryAttempts, this.retryPeriod, this.lastFailedRevivalTime, this.unhealthySince, new CheckpointerStatus(this.Checkpointer.Id, this.Checkpointer.Offset, this.Checkpointer.Proposed)); public EndpointExecutorFsm(Endpoint endpoint, ICheckpointer checkpointer, EndpointExecutorConfig config) : this(endpoint, checkpointer, config, SystemTime.Instance) @@ -69,6 +117,13 @@ public EndpointExecutorFsm(Endpoint endpoint, ICheckpointer checkpointer, Endpoi } } + public Endpoint Endpoint => this.processor.Endpoint; + + public ICheckpointer Checkpointer { get; } + + public EndpointExecutorStatus Status => + new EndpointExecutorStatus(this.Endpoint.Id, this.state, this.retryAttempts, this.retryPeriod, this.lastFailedRevivalTime, this.unhealthySince, new CheckpointerStatus(this.Checkpointer.Id, this.Checkpointer.Offset, this.Checkpointer.Proposed)); + public async Task RunAsync(ICommand command) { using (await this.sync.LockAsync()) @@ -83,7 +138,6 @@ public async Task RunAsync(ICommand command) protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.state == State.Closed); if (disposing) { this.Checkpointer.Dispose(); @@ -92,69 +146,6 @@ protected virtual void Dispose(bool disposing) } } - bool ShouldRetry(Exception exception, out TimeSpan retryAfter) - { - retryAfter = TimeSpan.Zero; - ITransientErrorDetectionStrategy detectionStrategy = this.processor.ErrorDetectionStrategy; - ShouldRetry shouldRetry = this.config.RetryStrategy.GetShouldRetry(); - return detectionStrategy.IsTransient(exception) && shouldRetry(this.retryAttempts, exception, out retryAfter); - } - - static readonly IReadOnlyDictionary Actions = new Dictionary - { - { State.Idle, new StateActions(EnterIdleAsync, StateActions.NullAction) }, - { State.Sending, new StateActions(EnterSendingAsync, StateActions.NullAction) }, - { State.Checkpointing, new StateActions(EnterCheckpointingAsync, StateActions.NullAction) }, - { State.Failing, new StateActions(EnterFailingAsync, ExitFailingAsync) }, - { State.DeadCheckpointing, new StateActions(EnterDeadCheckpointingAsync, ExitDeadCheckpointingAsync) }, - { State.DeadIdle, new StateActions(StateActions.NullAction, StateActions.NullAction) }, - { State.DeadProcess, new StateActions(EnterProcessDeadAsync, StateActions.NullAction) }, - { State.Closed, new StateActions(EnterClosedAsync, StateActions.NullAction) } - }; - - static readonly IReadOnlyDictionary Transitions = new Dictionary - { - // Idle - { new StateCommandPair(State.Idle, CommandType.SendMessage), new StateTransition(State.Sending, PrepareForSendAsync) }, - { new StateCommandPair(State.Idle, CommandType.UpdateEndpoint), new StateTransition(State.Idle, UpdateEndpointAsync) }, - { new StateCommandPair(State.Idle, CommandType.Close), new StateTransition(State.Closed) }, - - // Sending - { new StateCommandPair(State.Sending, CommandType.Checkpoint), new StateTransition(State.Checkpointing, PrepareForCheckpointAsync) }, - { new StateCommandPair(State.Sending, CommandType.Throw), new StateTransition(State.Idle, ThrowCompleteAsync) }, - { new StateCommandPair(State.Sending, CommandType.Fail), new StateTransition(State.Failing, FailAsync) }, - { new StateCommandPair(State.Sending, CommandType.Die), new StateTransition(State.DeadProcess, DieAsync) }, - - // Checkpointing - { new StateCommandPair(State.Checkpointing, CommandType.Succeed), new StateTransition(State.Idle, SucceedCompleteAsync) }, - { new StateCommandPair(State.Checkpointing, CommandType.Fail), new StateTransition(State.Failing, FailAsync) }, - { new StateCommandPair(State.Checkpointing, CommandType.Die), new StateTransition(State.DeadProcess, DieAsync) }, - { new StateCommandPair(State.Checkpointing, CommandType.Throw), new StateTransition(State.Idle, ThrowCompleteAsync) }, - { new StateCommandPair(State.Checkpointing, CommandType.SendMessage), new StateTransition(State.Sending, PrepareForSendAsync) }, - - // Failing - { new StateCommandPair(State.Failing, CommandType.Retry), new StateTransition(State.Sending) }, - { new StateCommandPair(State.Failing, CommandType.UpdateEndpoint), new StateTransition(State.Sending, UpdateEndpointAsync) }, - { new StateCommandPair(State.Failing, CommandType.Die), new StateTransition(State.DeadProcess, DieAsync) }, - { new StateCommandPair(State.Failing, CommandType.Close), new StateTransition(State.Closed) }, - - // Idle Dead - { new StateCommandPair(State.DeadIdle, CommandType.SendMessage), new StateTransition(State.DeadProcess, PrepareForSendAsync) }, - { new StateCommandPair(State.DeadIdle, CommandType.UpdateEndpoint), new StateTransition(State.Idle, UpdateEndpointAsync) }, - { new StateCommandPair(State.DeadIdle, CommandType.Close), new StateTransition(State.Closed) }, - - // ProcessDead - { new StateCommandPair(State.DeadProcess, CommandType.Revive), new StateTransition(State.Sending, PrepareForReviveAsync) }, - { new StateCommandPair(State.DeadProcess, CommandType.Checkpoint), new StateTransition(State.DeadCheckpointing, PrepareForCheckpointAsync) }, - - // DeadCheckpointing - { new StateCommandPair(State.DeadCheckpointing, CommandType.DeadSucceed), new StateTransition(State.DeadIdle) }, - - // Closed - { new StateCommandPair(State.Closed, CommandType.SendMessage), new StateTransition(State.Closed, SendMessageClosedAsync) }, - - }; - static Task PrepareForSendAsync(EndpointExecutorFsm thisPtr, ICommand command) { Preconditions.CheckNotNull(thisPtr); @@ -269,19 +260,6 @@ static Task FailAsync(EndpointExecutorFsm thisPtr, ICommand command) return TaskEx.Done; } - async void RetryAsync(object obj) - { - try - { - this.retryTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); - await this.RunAsync(Commands.Retry); - } - catch (Exception ex) - { - Events.RetryFailed(this, ex); - } - } - static Task DieAsync(EndpointExecutorFsm thisPtr, ICommand command) { Preconditions.CheckNotNull(thisPtr); @@ -342,6 +320,7 @@ static async Task EnterSendingAsync(EndpointExecutorFsm thisPtr) : thisPtr.unhealthySince; Events.SendFailure(thisPtr, result, stopwatch); } + next = Commands.Checkpoint(result); } else @@ -366,6 +345,7 @@ static async Task EnterSendingAsync(EndpointExecutorFsm thisPtr) : thisPtr.unhealthySince; next = thisPtr.config.ThrowOnDead ? (ICommand)Commands.Throw(ex) : Commands.Die; } + await RunInternalAsync(thisPtr, next); } @@ -429,6 +409,7 @@ static async Task EnterCheckpointingAsync(EndpointExecutorFsm thisPtr) ? Commands.Throw(ex) : EnterCheckpointingHelper(thisPtr); } + await RunInternalAsync(thisPtr, next); } @@ -549,6 +530,27 @@ static async Task RunInternalAsync(EndpointExecutorFsm thisPtr, ICommand command Events.StateEnter(thisPtr); } + bool ShouldRetry(Exception exception, out TimeSpan retryAfter) + { + retryAfter = TimeSpan.Zero; + ITransientErrorDetectionStrategy detectionStrategy = this.processor.ErrorDetectionStrategy; + ShouldRetry shouldRetry = this.config.RetryStrategy.GetShouldRetry(); + return detectionStrategy.IsTransient(exception) && shouldRetry(this.retryAttempts, exception, out retryAfter); + } + + async void RetryAsync(object obj) + { + try + { + this.retryTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + await this.RunAsync(Commands.Retry); + } + catch (Exception ex) + { + Events.RetryFailed(this, ex); + } + } + static class Events { const string DateTimeFormat = "o"; @@ -603,8 +605,13 @@ public static void StateTransition(EndpointExecutorFsm fsm, State from, State to public static void Send(EndpointExecutorFsm fsm, ICollection messages, ICollection admitted) { - Log.LogDebug((int)EventIds.Send, "[Send Sending began. BatchSize: {0}, AdmittedSize: {1}, MaxAdmittedOffset: {2}, {3}", - messages.Count, admitted.Count, admitted.Max(m => m.Offset), GetContextString(fsm)); + Log.LogDebug( + (int)EventIds.Send, + "[Send Sending began. BatchSize: {0}, AdmittedSize: {1}, MaxAdmittedOffset: {2}, {3}", + messages.Count, + admitted.Count, + admitted.Max(m => m.Offset), + GetContextString(fsm)); } public static void SendSuccess(EndpointExecutorFsm fsm, ICollection admitted, ISinkResult result, Stopwatch stopwatch) @@ -616,8 +623,14 @@ public static void SendSuccess(EndpointExecutorFsm fsm, ICollection ad Log.LogError((int)EventIds.CounterFailure, "[LogExternalWriteLatencyCounterFailed] {0}", error); } - Log.LogDebug((int)EventIds.SendSuccess, "[SendSuccess] Sending succeeded. AdmittedSize: {0}, SuccessfulSize: {1}, FailedSize: {2}, InvalidSize: {3}, {4}", - admitted.Count, result.Succeeded.Count, result.Failed.Count, result.InvalidDetailsList.Count, GetContextString(fsm)); + Log.LogDebug( + (int)EventIds.SendSuccess, + "[SendSuccess] Sending succeeded. AdmittedSize: {0}, SuccessfulSize: {1}, FailedSize: {2}, InvalidSize: {3}, {4}", + admitted.Count, + result.Succeeded.Count, + result.Failed.Count, + result.InvalidDetailsList.Count, + GetContextString(fsm)); } public static void SendFailureUnhandledException(EndpointExecutorFsm fsm, ICollection messages, Stopwatch stopwatch, Exception exception) @@ -649,8 +662,14 @@ public static void SendFailure(EndpointExecutorFsm fsm, ISinkResult re Routing.UserAnalyticsLogger.LogInvalidMessage(fsm.Endpoint.IotHubName, invalidDetails.Item, invalidDetails.FailureKind); } - Log.LogWarning((int)EventIds.SendFailure, failureDetails.RawException, "[SendFailure] Sending failed. SuccessfulSize: {0}, FailedSize: {1}, InvalidSize: {2}, {3}", - result.Succeeded.Count, result.Failed.Count, result.InvalidDetailsList.Count, GetContextString(fsm)); + Log.LogWarning( + (int)EventIds.SendFailure, + failureDetails.RawException, + "[SendFailure] Sending failed. SuccessfulSize: {0}, FailedSize: {1}, InvalidSize: {2}, {3}", + result.Succeeded.Count, + result.Failed.Count, + result.InvalidDetailsList.Count, + GetContextString(fsm)); LogUnhealthyEndpointOpMonError(fsm, failureDetails.FailureKind); } @@ -662,14 +681,22 @@ public static void SendNone(EndpointExecutorFsm fsm) public static void Checkpoint(EndpointExecutorFsm fsm, ISinkResult result) { - Log.LogDebug((int)EventIds.Checkpoint, "[Checkpoint] Checkpointing began. CheckpointOffset: {0}, SuccessfulSize: {1}, RemainingSize: {2}, {3}", - fsm.Status.CheckpointerStatus.Offset, result.Succeeded.Count + result.InvalidDetailsList.Count, result.Failed.Count, GetContextString(fsm)); + Log.LogDebug( + (int)EventIds.Checkpoint, + "[Checkpoint] Checkpointing began. CheckpointOffset: {0}, SuccessfulSize: {1}, RemainingSize: {2}, {3}", + fsm.Status.CheckpointerStatus.Offset, + result.Succeeded.Count + result.InvalidDetailsList.Count, + result.Failed.Count, + GetContextString(fsm)); } public static void CheckpointSuccess(EndpointExecutorFsm fsm, ISinkResult result) { - Log.LogInformation((int)EventIds.CheckpointSuccess, "[CheckpointSuccess] Checkpointing succeeded. CheckpointOffset: {0}, {1}", - fsm.Status.CheckpointerStatus.Offset, GetContextString(fsm)); + Log.LogInformation( + (int)EventIds.CheckpointSuccess, + "[CheckpointSuccess] Checkpointing succeeded. CheckpointOffset: {0}, {1}", + fsm.Status.CheckpointerStatus.Offset, + GetContextString(fsm)); IList invalidMessages = result.InvalidDetailsList.Select(d => d.Item).ToList(); @@ -683,8 +710,12 @@ public static void CheckpointSuccess(EndpointExecutorFsm fsm, ISinkResult messages) Preconditions.CheckArgument(fsm.Status.LastFailedRevivalTime.HasValue); DateTime reviveAt = fsm.Status.LastFailedRevivalTime.GetOrElse(fsm.systemTime.UtcNow).SafeAdd(fsm.config.RevivePeriod); - Log.LogWarning((int)EventIds.Dead, "[Dead] Dropping {0} messages. BatchSize: {1}, LastFailedRevivalTime: {2}, UnhealthySince: {3}, ReviveAt: {4}, {5}", - messages.Count, messages.Count, fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat), - fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat), reviveAt.ToString(DateTimeFormat), GetContextString(fsm)); + Log.LogWarning( + (int)EventIds.Dead, + "[Dead] Dropping {0} messages. BatchSize: {1}, LastFailedRevivalTime: {2}, UnhealthySince: {3}, ReviveAt: {4}, {5}", + messages.Count, + messages.Count, + fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat), + fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat), + reviveAt.ToString(DateTimeFormat), + GetContextString(fsm)); } public static void DeadSuccess(EndpointExecutorFsm fsm, ICollection messages) @@ -730,9 +777,14 @@ public static void DeadSuccess(EndpointExecutorFsm fsm, ICollection me CultureInfo culture = CultureInfo.InvariantCulture; DateTime reviveAt = fsm.Status.LastFailedRevivalTime.GetOrElse(fsm.systemTime.UtcNow).SafeAdd(fsm.config.RevivePeriod); - Log.LogWarning((int)EventIds.DeadSuccess, "[DeadSuccess] Dropped {0} messages. BatchSize: {1}, LastFailedRevivalTime: {2}, UnhealthySince: {3}, ReviveAt: {4}, {5}", - messages.Count, messages.Count, fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), - fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), reviveAt.ToString(DateTimeFormat, culture), + Log.LogWarning( + (int)EventIds.DeadSuccess, + "[DeadSuccess] Dropped {0} messages. BatchSize: {1}, LastFailedRevivalTime: {2}, UnhealthySince: {3}, ReviveAt: {4}, {5}", + messages.Count, + messages.Count, + fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), + fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), + reviveAt.ToString(DateTimeFormat, culture), GetContextString(fsm)); SetProcessingInternalCounters(fsm, "Dropped", messages); @@ -753,9 +805,14 @@ public static void DeadFailure(EndpointExecutorFsm fsm, Exception ex) CultureInfo culture = CultureInfo.InvariantCulture; DateTime reviveAt = fsm.Status.LastFailedRevivalTime.GetOrElse(fsm.systemTime.UtcNow).SafeAdd(fsm.config.RevivePeriod); - Log.LogError((int)EventIds.DeadFailure, ex, "[DeadFailure] Dropping messages failed. LastFailedRevivalTime: {0}, UnhealthySince: {1}, DeadTime:{2}, ReviveAt: {3}, {4}", - fsm.Status.LastFailedRevivalTime.ToString(), fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), - fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), reviveAt.ToString(DateTimeFormat, culture), + Log.LogError( + (int)EventIds.DeadFailure, + ex, + "[DeadFailure] Dropping messages failed. LastFailedRevivalTime: {0}, UnhealthySince: {1}, DeadTime:{2}, ReviveAt: {3}, {4}", + fsm.Status.LastFailedRevivalTime.ToString(), + fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), + fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), + reviveAt.ToString(DateTimeFormat, culture), GetContextString(fsm)); } @@ -771,10 +828,13 @@ public static void PrepareForRevive(EndpointExecutorFsm fsm) CultureInfo culture = CultureInfo.InvariantCulture; DateTime reviveAt = fsm.Status.LastFailedRevivalTime.GetOrElse(fsm.systemTime.UtcNow).SafeAdd(fsm.config.RevivePeriod); - Log.LogInformation((int)EventIds.PrepareForRevive, "[PrepareForRevive] Attempting to bring endpoint back. LastFailedRevivalTime: {0}, UnhealthySince: {1}, ReviveAt: {2}, {3}", + Log.LogInformation( + (int)EventIds.PrepareForRevive, + "[PrepareForRevive] Attempting to bring endpoint back. LastFailedRevivalTime: {0}, UnhealthySince: {1}, ReviveAt: {2}, {3}", fsm.Status.LastFailedRevivalTime.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), fsm.Status.UnhealthySince.GetOrElse(Checkpointers.Checkpointer.DateTimeMinValue).ToString(DateTimeFormat, culture), - reviveAt.ToString(DateTimeFormat, culture), GetContextString(fsm)); + reviveAt.ToString(DateTimeFormat, culture), + GetContextString(fsm)); } public static void Revived(EndpointExecutorFsm fsm) @@ -809,8 +869,13 @@ static void LogUnhealthyEndpointOpMonError(EndpointExecutorFsm fsm, FailureKind static string GetContextString(EndpointExecutorFsm fsm) { - return string.Format(CultureInfo.InvariantCulture, "EndpointId: {0}, EndpointName: {1}, CheckpointerId: {2}, State: {3}", - fsm.Status.Id, fsm.Endpoint.Name, fsm.Status.CheckpointerStatus.Id, fsm.state); + return string.Format( + CultureInfo.InvariantCulture, + "EndpointId: {0}, EndpointName: {1}, CheckpointerId: {2}, State: {3}", + fsm.Status.Id, + fsm.Endpoint.Name, + fsm.Status.CheckpointerStatus.Id, + fsm.state); } static void SetProcessingInternalCounters(EndpointExecutorFsm fsm, string status, ICollection messages) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/ICommand.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/ICommand.cs index af102cd3e94..304ed747515 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/ICommand.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/ICommand.cs @@ -30,12 +30,6 @@ public class SendMessage : ICommand { readonly TaskCompletionSource tcs; - public CommandType Type => CommandType.SendMessage; - - public ICollection Messages { get; } - - public Task Completion => this.tcs.Task; - public SendMessage(ICollection messages) : this(messages, new TaskCompletionSource()) { @@ -47,6 +41,12 @@ public SendMessage(ICollection messages) this.tcs = Preconditions.CheckNotNull(tcs); } + public CommandType Type => CommandType.SendMessage; + + public ICollection Messages { get; } + + public Task Completion => this.tcs.Task; + /// /// Copies send message command and task completion source. /// This is to allow resending of messages with the same task completion source. @@ -65,26 +65,26 @@ public SendMessage Copy(ICollection messages) public class UpdateEndpoint : ICommand { - public CommandType Type => CommandType.UpdateEndpoint; - - public Endpoint Endpoint { get; } - public UpdateEndpoint(Endpoint endpoint) { this.Endpoint = Preconditions.CheckNotNull(endpoint); } + + public CommandType Type => CommandType.UpdateEndpoint; + + public Endpoint Endpoint { get; } } public class Checkpoint : ICommand { - public CommandType Type => CommandType.Checkpoint; - - public ISinkResult Result { get; } - public Checkpoint(ISinkResult result) { this.Result = Preconditions.CheckNotNull(result); } + + public CommandType Type => CommandType.Checkpoint; + + public ISinkResult Result { get; } } public class Succeed : ICommand @@ -99,26 +99,26 @@ public class DeadSucceed : ICommand public class Fail : ICommand { - public CommandType Type => CommandType.Fail; - - public TimeSpan RetryAfter { get; } - public Fail(TimeSpan retryAfter) { this.RetryAfter = retryAfter; } + + public CommandType Type => CommandType.Fail; + + public TimeSpan RetryAfter { get; } } public class Throw : ICommand { - public CommandType Type => CommandType.Throw; - - public Exception Exception { get; } - public Throw(Exception exception) { this.Exception = exception; } + + public CommandType Type => CommandType.Throw; + + public Exception Exception { get; } } public class Retry : ICommand @@ -143,26 +143,26 @@ public class Close : ICommand public static class Commands { - public static SendMessage SendMessage(params IMessage[] messages) => new SendMessage(messages); + public static Succeed Succeed { get; } = new Succeed(); - public static UpdateEndpoint UpdateEndpoint(Endpoint endpoint) => new UpdateEndpoint(endpoint); + public static DeadSucceed DeadSucceed { get; } = new DeadSucceed(); - public static Checkpoint Checkpoint(ISinkResult result) => new Checkpoint(result); + public static Retry Retry { get; } = new Retry(); - public static Throw Throw(Exception exception) => new Throw(exception); + public static Revive Revive { get; } = new Revive(); - public static Fail Fail(TimeSpan retryAfter) => new Fail(retryAfter); + public static Die Die { get; } = new Die(); - public static Succeed Succeed { get; } = new Succeed(); + public static Close Close { get; } = new Close(); - public static DeadSucceed DeadSucceed { get; } = new DeadSucceed(); + public static SendMessage SendMessage(params IMessage[] messages) => new SendMessage(messages); - public static Retry Retry { get; }= new Retry(); + public static UpdateEndpoint UpdateEndpoint(Endpoint endpoint) => new UpdateEndpoint(endpoint); - public static Revive Revive { get; }= new Revive(); + public static Checkpoint Checkpoint(ISinkResult result) => new Checkpoint(result); - public static Die Die { get; } = new Die(); + public static Throw Throw(Exception exception) => new Throw(exception); - public static Close Close { get; }= new Close(); + public static Fail Fail(TimeSpan retryAfter) => new Fail(retryAfter); } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateActions.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateActions.cs index c527c60514b..7bc3add4a06 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateActions.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateActions.cs @@ -12,9 +12,7 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine /// class StateActions { - public Func Enter { get; } - - public Func Exit { get; } + public static readonly StateActions Null = new StateActions(); public StateActions() : this(NullAction, NullAction) @@ -27,8 +25,10 @@ public StateActions(Func enter, Func Enter { get; } + + public Func Exit { get; } public static Task NullAction(EndpointExecutorFsm machine) => TaskEx.Done; } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateCommandPair.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateCommandPair.cs index 01b21d5648c..73935025a58 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateCommandPair.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateCommandPair.cs @@ -19,6 +19,16 @@ public StateCommandPair(State state, CommandType command) this.command = command; } + public static bool operator ==(StateCommandPair pair1, StateCommandPair pair2) + { + return pair1.Equals(pair2); + } + + public static bool operator !=(StateCommandPair pair1, StateCommandPair pair2) + { + return !pair1.Equals(pair2); + } + public bool Equals(StateCommandPair other) { return this.state == other.state && this.command == other.command; @@ -37,17 +47,7 @@ public override int GetHashCode() } } - public static bool operator ==(StateCommandPair pair1, StateCommandPair pair2) - { - return pair1.Equals(pair2); - } - - public static bool operator !=(StateCommandPair pair1, StateCommandPair pair2) - { - return !pair1.Equals(pair2); - } - public override string ToString() => string.Format(CultureInfo.InvariantCulture, "StateCommandPair({0}, {1})", this.state, this.command); } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateTransition.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateTransition.cs index 7d5232744e6..7d367a2f07d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateTransition.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/endpoints/statemachine/StateTransition.cs @@ -11,10 +11,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine /// class StateTransition { - public State NextState { get; } - - public Func TransitionAction { get; } - public StateTransition(State nextState) : this(nextState, NullAction) { @@ -26,6 +22,10 @@ public StateTransition(State nextState, Func TransitionAction { get; } + public static Task NullAction(EndpointExecutorFsm machine, ICommand command) => TaskEx.Done; } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/BaseMessageSource.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/BaseMessageSource.cs index 0fb191c57e6..f939dad89ab 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/BaseMessageSource.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/BaseMessageSource.cs @@ -12,22 +12,20 @@ protected BaseMessageSource(string source) } public string Source { get; } - + public virtual bool Match(IMessageSource messageSource) { Preconditions.CheckNotNull(messageSource, nameof(messageSource)); var baseMessageSource = messageSource as BaseMessageSource; return baseMessageSource?.Source != null && - AppendSingleTrailingSlash(baseMessageSource.Source).StartsWith(this.Source, StringComparison.OrdinalIgnoreCase); + AppendSingleTrailingSlash(baseMessageSource.Source).StartsWith(this.Source, StringComparison.OrdinalIgnoreCase); } - static string AppendSingleTrailingSlash(string value) => value.Trim().TrimEnd('/') + "/"; - public override bool Equals(object obj) { var baseMessageSource = obj as BaseMessageSource; return baseMessageSource?.Source != null && - AppendSingleTrailingSlash(baseMessageSource.Source).Equals(this.Source, StringComparison.OrdinalIgnoreCase); + AppendSingleTrailingSlash(baseMessageSource.Source).Equals(this.Source, StringComparison.OrdinalIgnoreCase); } public override int GetHashCode() @@ -41,5 +39,7 @@ public override int GetHashCode() } public override string ToString() => this.GetType().Name; - } -} \ No newline at end of file + + static string AppendSingleTrailingSlash(string value) => value.Trim().TrimEnd('/') + "/"; + } +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/IMessageSource.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/IMessageSource.cs index 951b7aa36b9..bba59ab570a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/IMessageSource.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/IMessageSource.cs @@ -1,8 +1,8 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.MessageSources { public interface IMessageSource { bool Match(IMessageSource messageSource); - } + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/MessageSourceExtensions.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/MessageSourceExtensions.cs index 38abd6d48a3..ff0837120a1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/MessageSourceExtensions.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/messageSources/MessageSourceExtensions.cs @@ -4,7 +4,7 @@ namespace Microsoft.Azure.Devices.Routing.Core.MessageSources public static class MessageSourceExtensions { public static bool IsTelemetry(this IMessageSource messageSource) => messageSource != null - && messageSource is BaseMessageSource baseMessageSource - && baseMessageSource.Source.StartsWith("/messages/"); + && messageSource is BaseMessageSource baseMessageSource + && baseMessageSource.Source.StartsWith("/messages/"); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Bool.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Bool.cs index 93016d3383f..96752b5d0f5 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Bool.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Bool.cs @@ -5,11 +5,10 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query public struct Bool { - readonly int value; - public static readonly Bool Undefined = new Bool(0); public static readonly Bool False = new Bool(-1); public static readonly Bool True = new Bool(1); + readonly int value; Bool(int value) { @@ -27,6 +26,7 @@ public struct Bool { return Undefined; } + return x.value == y.value ? True : False; } @@ -36,6 +36,7 @@ public struct Bool { return Undefined; } + return x.value != y.value ? True : False; } @@ -54,6 +55,7 @@ public struct Bool { return x.value == -1 ? False : Undefined; } + return x.value == -1 || y.value == -1 ? False : True; } @@ -67,6 +69,7 @@ public struct Bool { return x.value == 1 ? True : Undefined; } + return x.value == 1 || y.value == 1 ? True : False; } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/CompareOp.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/CompareOp.cs index d9d28d13466..850951da102 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/CompareOp.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/CompareOp.cs @@ -9,5 +9,5 @@ public enum CompareOp Le, Gt, Ge - }; + } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ComparisonOperators.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ComparisonOperators.cs index 59362dcb3b0..4042a5b88ab 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ComparisonOperators.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ComparisonOperators.cs @@ -39,6 +39,7 @@ public static Bool Compare(CompareOp op, string s1, string s2) default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } + return result; } @@ -77,6 +78,7 @@ public static Bool Compare(CompareOp op, double d1, double d2) default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } + return result; } @@ -115,6 +117,7 @@ public static Bool Compare(CompareOp op, QueryValue v1, QueryValue v2) default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } + return result; } @@ -153,6 +156,7 @@ public static Bool Compare(CompareOp op, Bool d1, Bool d2) default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } + return result; } @@ -178,6 +182,7 @@ public static Bool Compare(CompareOp op, Null n1, Null n2) default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } + return result; } @@ -201,6 +206,7 @@ public static Bool Compare(CompareOp op, Undefined u1, Undefined u2) default: throw new ArgumentOutOfRangeException(nameof(op), op, null); } + return result; } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ConditionVisitor.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ConditionVisitor.cs index 49e5626e5e8..23df773457a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ConditionVisitor.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ConditionVisitor.cs @@ -122,7 +122,6 @@ public override Expression VisitSysProperty(ConditionParser.SysPropertyContext c // the app property takes precedence // We must check whether the property first exists as a user property. If it does, return it, else // try to get it from the body query or the system properties - string propertyName = context.GetText(); Expression alternative; @@ -164,61 +163,6 @@ public override Expression VisitSysPropertyEscaped(ConditionParser.SysPropertyEs return sysPropertyExpression; } - Expression GetProperty(Expression propertyBag, Expression property) - { - return this.GetPropertyOrElse(propertyBag, property, UndefinedExpression, false); - } - - Expression GetPropertyOrElse(Expression propertyBag, Expression property, Expression alternative, bool isBodyQuery) - { - Expression expression; - - if (propertyBag.Type != typeof(Undefined)) - { - MethodInfo method = typeof(IReadOnlyDictionary).GetMethod("ContainsKey"); - MethodCallExpression contains = Expression.Call(propertyBag, method, property); - IndexExpression value = Expression.Property(propertyBag, "Item", property); - - // NOTE: Arguments to Expression.Condition need to be of the same type. So we will wrap to QueryValue if it could be a bodyquery - - // Review following cases before making changes here - - // 1. It is an app property with no conflict => Convert to String and return value and alternative - // 2. It is a system property with no conflict => Same as case 1. Property bag supplied by caller is sys property - // 3. It is a Body query with no conflict => ifFalse Expression will be returned. Type is already QueryValue. - // 4. It looks like a body query but is an app property ($body.propertyname) => - // ifTrue will be evaluated. Just wrap contents to QueryValue so that Expression.Condition does not complain. - // 5. Body Query and a conflicting app property => App property wins - // 6. Body Query escaped and a conflicting app property => Body Query wins - - Expression ifTrue = isBodyQuery ? Expression.Convert(value, typeof(QueryValue)) : Expression.Convert(value, typeof(string)); - Expression ifFalse = isBodyQuery ? alternative : Expression.Convert(alternative, typeof(string)); - - expression = Expression.Condition( - contains, - ifTrue, - ifFalse); - } - else - { - expression = UndefinedExpression; - } - return expression; - } - - Expression GetSysProperty(string propertyName) - { - // SystemProperty name containing '[' or ']' can reach here if it looked like body query, and was parsed successfully using Condition.g4. - // In this case, return Undefined because it is not a supported SystemProperty. - if (propertyName.Contains("[") || propertyName.Contains("]")) - { - return UndefinedExpression; - } - - Expression propertyBag = Expression.Property(this.message, "SystemProperties"); - Expression property = Expression.Constant(propertyName); - return this.GetProperty(propertyBag, property); - } - // Functions public override Expression VisitFunc(ConditionParser.FuncContext context) { @@ -305,6 +249,7 @@ public override Expression VisitMulDivMod(ConditionParser.MulDivModContext conte { result = UndefinedExpression; } + return result; } @@ -328,6 +273,7 @@ public override Expression VisitAddSubConcat(ConditionParser.AddSubConcatContext default: throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unrecognized op token: {0}", context.op.Text)); } + return result; } @@ -363,6 +309,7 @@ public override Expression VisitCompare(ConditionParser.CompareContext context) { result = UndefinedExpression; } + return result; } @@ -393,6 +340,7 @@ public override Expression VisitEquality(ConditionParser.EqualityContext context { result = UndefinedExpression; } + return result; } @@ -433,6 +381,7 @@ public override Expression VisitCoalesce(ConditionParser.CoalesceContext context { result = UndefinedExpression; } + return result; } @@ -500,6 +449,61 @@ static string Unescape(string input) return result2; } + Expression GetProperty(Expression propertyBag, Expression property) + { + return this.GetPropertyOrElse(propertyBag, property, UndefinedExpression, false); + } + + Expression GetPropertyOrElse(Expression propertyBag, Expression property, Expression alternative, bool isBodyQuery) + { + Expression expression; + + if (propertyBag.Type != typeof(Undefined)) + { + MethodInfo method = typeof(IReadOnlyDictionary).GetMethod("ContainsKey"); + MethodCallExpression contains = Expression.Call(propertyBag, method, property); + IndexExpression value = Expression.Property(propertyBag, "Item", property); + + // NOTE: Arguments to Expression.Condition need to be of the same type. So we will wrap to QueryValue if it could be a bodyquery + + // Review following cases before making changes here - + // 1. It is an app property with no conflict => Convert to String and return value and alternative + // 2. It is a system property with no conflict => Same as case 1. Property bag supplied by caller is sys property + // 3. It is a Body query with no conflict => ifFalse Expression will be returned. Type is already QueryValue. + // 4. It looks like a body query but is an app property ($body.propertyname) => + // ifTrue will be evaluated. Just wrap contents to QueryValue so that Expression.Condition does not complain. + // 5. Body Query and a conflicting app property => App property wins + // 6. Body Query escaped and a conflicting app property => Body Query wins + Expression ifTrue = isBodyQuery ? Expression.Convert(value, typeof(QueryValue)) : Expression.Convert(value, typeof(string)); + Expression ifFalse = isBodyQuery ? alternative : Expression.Convert(alternative, typeof(string)); + + expression = Expression.Condition( + contains, + ifTrue, + ifFalse); + } + else + { + expression = UndefinedExpression; + } + + return expression; + } + + Expression GetSysProperty(string propertyName) + { + // SystemProperty name containing '[' or ']' can reach here if it looked like body query, and was parsed successfully using Condition.g4. + // In this case, return Undefined because it is not a supported SystemProperty. + if (propertyName.Contains("[") || propertyName.Contains("]")) + { + return UndefinedExpression; + } + + Expression propertyBag = Expression.Property(this.message, "SystemProperties"); + Expression property = Expression.Constant(propertyName); + return this.GetProperty(propertyBag, property); + } + Expression GetBuiltin(string name, IToken token, params Expression[] args) { IBuiltin builtin; @@ -517,8 +521,8 @@ Expression GetBuiltin(string name, IToken token, params Expression[] args) bool TryGetSupportedBuiltin(string name, out IBuiltin builtin) { return Builtins.TryGetValue(name, out builtin) && - builtin.IsEnabled(this.routeCompilerFlags) && - builtin.IsValidMessageSource(this.route.Source); + builtin.IsEnabled(this.routeCompilerFlags) && + builtin.IsValidMessageSource(this.route.Source); } bool CheckOperand(IToken token, Type expected, Expression expr) @@ -531,6 +535,7 @@ bool CheckOperand(IToken token, Type expected, Expression expr) { this.errors.OperandError(token, required, given); } + return isValid; } @@ -549,6 +554,7 @@ bool CheckOperands(IToken token, Type expected, ref Expression left, ref Express { right = Expression.Convert(right, typeof(QueryValue)); } + if (right.Type == typeof(QueryValue) && left.Type != typeof(QueryValue)) { left = Expression.Convert(left, typeof(QueryValue)); @@ -573,6 +579,7 @@ bool CheckBooleanOperands(IToken token, ref Expression left, ref Expression righ { right = Expression.Convert(right, typeof(QueryValue)); } + if (right.Type == typeof(QueryValue) && left.Type != typeof(QueryValue)) { left = Expression.Convert(left, typeof(QueryValue)); diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ErrorListener.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ErrorListener.cs index 8081ad69f3e..d0613a17200 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ErrorListener.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/ErrorListener.cs @@ -116,6 +116,16 @@ public void InvalidBuiltinError(IToken token) this.Error(token, string.Format(CultureInfo.InvariantCulture, "Invalid built-in function '{0}'", token.Text)); } + static string TypeName(Type type) + { + return type == typeof(string) ? "string" : + type == typeof(double) ? "number" : + type == typeof(Bool) ? "bool" : + type == typeof(Undefined) ? "undefined" : + type == typeof(Null) ? "null" : + "unknown"; + } + void Error(IToken offendingSymbol, string message) { // A couple things to note about the start and end column range: @@ -137,15 +147,5 @@ void Error(IToken offendingSymbol, string message) var error = new CompilationError(ErrorSeverity.Error, message, new ErrorRange(start, end)); this.errors.Add(error); } - - static string TypeName(Type type) - { - return type == typeof(string) ? "string" : - type == typeof(double) ? "number" : - type == typeof(Bool) ? "bool" : - type == typeof(Undefined) ? "undefined" : - type == typeof(Null) ? "null" : - "unknown"; - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/RouteCompilationException.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/RouteCompilationException.cs index 68996f0c712..06a521fbc27 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/RouteCompilationException.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/RouteCompilationException.cs @@ -12,13 +12,13 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query public class RouteCompilationException : Exception { - public IList Errors { get; } - public RouteCompilationException(IEnumerable errors) { this.Errors = Preconditions.CheckNotNull(errors).ToImmutableList(); } + public IList Errors { get; } + public override string ToString() { var sb = new StringBuilder(); @@ -26,7 +26,9 @@ public override string ToString() { sb.AppendLine("Compilation errors:"); foreach (string error in this.Errors.Select( - e => string.Format(CultureInfo.InvariantCulture, "StartLine[{0}], StartColumn[{1}], EndLine[{2}], EndColumn[{3}], Severity[{4}], Message: {5}", + e => string.Format( + CultureInfo.InvariantCulture, + "StartLine[{0}], StartColumn[{1}], EndLine[{2}], EndColumn[{3}], Severity[{4}], Message: {5}", e.Location.Start.Line, e.Location.Start.Column, e.Location.End.Line, diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Undefined.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Undefined.cs index bf3e58faf40..018fb3a79da 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Undefined.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/Undefined.cs @@ -5,12 +5,12 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query public struct Undefined { - public static Undefined Instance { get; } = new Undefined(); - static readonly string UndefinedString = new string(new[] { 'u', 'n', 'd', 'e', 'f', 'i', 'n', 'e', 'd' }); static readonly Bool UndefinedBool = Bool.Undefined; static readonly double UndefinedDouble = double.NaN; + public static Undefined Instance { get; } = new Undefined(); + // Equal public static Bool operator ==(Undefined x, Undefined y) => Bool.Undefined; @@ -268,6 +268,7 @@ public struct Undefined public static Bool IsDefined(double input) => (Bool)!double.IsNaN(input); public static Bool IsDefined(Bool input) => (Bool)!input.Equals(UndefinedBool); + public static Bool IsDefined(QueryValue input) => (Bool)(input.ValueType != QueryValueType.None); public bool Equals(Undefined other) => true; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Abs.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Abs.cs index a3eb6081c9d..c68a0baf8da 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Abs.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Abs.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins public class Abs : Builtin { - protected override BuiltinExecutor[] Executors => new[] { new BuiltinExecutor diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/AnyArgs.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/AnyArgs.cs index 23fd9aa08a5..1249cd4d352 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/AnyArgs.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/AnyArgs.cs @@ -5,15 +5,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins public class AnyArgs : IArgs { - public Type[] Types { get; } = new Type[0]; - - public int Arity { get; } - public AnyArgs(int arity) { this.Arity = arity; } + public Type[] Types { get; } = new Type[0]; + + public int Arity { get; } + public bool Match(Type[] args, bool forceCheckQueryValue) { return args.Length == this.Arity; diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Args.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Args.cs index 159106b9b4f..05c795a4861 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Args.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Args.cs @@ -8,19 +8,20 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins public class Args : IArgs { - public Type[] Types { get; } - - public int Arity => this.Types.Length; - public Args(params Type[] args) { this.Types = Preconditions.CheckNotNull(args); } + public Type[] Types { get; } + + public int Arity => this.Types.Length; + public bool Match(Type[] args, bool matchQueryValue) { - return this.Types.Length == args.Length && this.Types.Zip(args, - (t, g) => t == g || IsMatchInternal(t, g, matchQueryValue)).All(_ => _); + return this.Types.Length == args.Length && this.Types.Zip( + args, + (t, g) => t == g || IsMatchInternal(t, g, matchQueryValue)).All(_ => _); } static bool IsMatchInternal(Type type1, Type type2, bool matchQueryValue) @@ -28,8 +29,8 @@ static bool IsMatchInternal(Type type1, Type type2, bool matchQueryValue) if (matchQueryValue) { return type1 == type2 || - (type2 == typeof(QueryValue) && QueryValue.IsSupportedType(type1)) || - (type1 == typeof(QueryValue) && QueryValue.IsSupportedType(type2)); + (type2 == typeof(QueryValue) && QueryValue.IsSupportedType(type1)) || + (type1 == typeof(QueryValue) && QueryValue.IsSupportedType(type2)); } else { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/BodyQuery.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/BodyQuery.cs index 6fc86deaec7..a6e443c55bf 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/BodyQuery.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/BodyQuery.cs @@ -13,6 +13,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins public class BodyQuery : Builtin { + public override bool IsBodyQuery => true; + protected override BuiltinExecutor[] Executors => new[] { new BuiltinExecutor @@ -23,8 +25,6 @@ public class BodyQuery : Builtin }, }; - public override bool IsBodyQuery => true; - public override bool IsEnabled(RouteCompilerFlags routeCompilerFlags) { return routeCompilerFlags.HasFlag(RouteCompilerFlags.BodyQuery); @@ -44,7 +44,8 @@ static Expression Create(Expression[] args, Expression[] contextArgs) if (!JsonPathValidator.IsSupportedJsonPath(queryString, out string errorDetails)) { throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, + string.Format( + CultureInfo.InvariantCulture, "Error in $body query. {0}", errorDetails)); } @@ -62,8 +63,7 @@ static QueryValue Runtime(string queryString, IMessage message, Route route) QueryValue queryValue = message.GetQueryValue(queryString); - return queryValue.ValueType == QueryValueType.Object ? - QueryValue.Undefined : queryValue; + return queryValue.ValueType == QueryValueType.Object ? QueryValue.Undefined : queryValue; } catch (Exception ex) when (!ex.IsFatal()) { @@ -74,8 +74,8 @@ static QueryValue Runtime(string queryString, IMessage message, Route route) static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.BodyQuery; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Builtin.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Builtin.cs index 6ee22dc6309..c9985136b9a 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Builtin.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Builtin.cs @@ -11,14 +11,14 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins public abstract class Builtin : IBuiltin { + public virtual bool IsBodyQuery => false; + protected static Expression True { get; } = Expression.Constant(Bool.True); protected static Expression False { get; } = Expression.Constant(Bool.False); protected abstract BuiltinExecutor[] Executors { get; } - public virtual bool IsBodyQuery => false; - public virtual bool IsValidMessageSource(IMessageSource source) { return true; @@ -39,7 +39,6 @@ public Expression Get(IToken token, Expression[] args, Expression[] contextArgs, BuiltinExecutor executor = this.Executors.FirstOrDefault(ex => ex.InputArgs.Match(types, ex.IsQueryValueSupported)); // contextArgs currently are very straightforward. BodyQuery and TwinChangeIncludes use them to get message body. // Not doing a match on internal args to retrieve the executor as of yet - if (executor != null) { if (executor.IsQueryValueSupported) @@ -62,17 +61,18 @@ public Expression Get(IToken token, Expression[] args, Expression[] contextArgs, static Expression[] WrapArgsAsQueryValue(Expression[] expressions) { - return expressions.Select(exp => - { - if (exp.Type == typeof(QueryValue)) + return expressions.Select( + exp => { - return exp; - } - else - { - return Expression.Convert(exp, typeof(QueryValue)); - } - }).ToArray(); + if (exp.Type == typeof(QueryValue)) + { + return exp; + } + else + { + return Expression.Convert(exp, typeof(QueryValue)); + } + }).ToArray(); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Concat.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Concat.cs index f8d19d98d22..48cee583f38 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Concat.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/Concat.cs @@ -28,8 +28,8 @@ static Expression Create(Expression[] args, Expression[] contextArgs) // ReSharper disable once UnusedMember.Local static string Runtime(QueryValue[] input) { - return input.All(s => s?.ValueType == QueryValueType.String && Undefined.IsDefined((string)s.Value)) ? - string.Concat(input.Select(_ => _.Value)) + return input.All(s => s?.ValueType == QueryValueType.String && Undefined.IsDefined((string)s.Value)) + ? string.Concat(input.Select(_ => _.Value)) : Undefined.Instance; } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/TwinChangeIncludes.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/TwinChangeIncludes.cs index 3a932d1e7a0..019fdf55dea 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/TwinChangeIncludes.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/TwinChangeIncludes.cs @@ -14,6 +14,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins public class TwinChangeIncludes : Builtin { + public override bool IsBodyQuery => true; + protected override BuiltinExecutor[] Executors => new[] { new BuiltinExecutor @@ -24,8 +26,6 @@ public class TwinChangeIncludes : Builtin }, }; - public override bool IsBodyQuery => true; - public override bool IsValidMessageSource(IMessageSource source) => source is TwinChangeEventMessageSource; public override bool IsEnabled(RouteCompilerFlags routeCompilerFlags) @@ -48,7 +48,8 @@ static Expression Create(Expression[] args, Expression[] contextArgs) if (!TwinChangeJsonPathValidator.IsSupportedJsonPath(queryString, out errorDetails)) { throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, + string.Format( + CultureInfo.InvariantCulture, "{0}", errorDetails)); } @@ -89,8 +90,8 @@ static Bool Runtime(string queryString, IMessage message, Route route) static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.TwinChangeIncludes; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/VarArgs.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/VarArgs.cs index 1a0fa0b92fd..495ce0a87da 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/VarArgs.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/builtins/VarArgs.cs @@ -14,21 +14,21 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Builtins /// public class VarArgs : IArgs { - public Type[] Types { get; } - - public int Arity => this.Types.Length; - public VarArgs(params Type[] args) { this.Types = Preconditions.CheckNotNull(args); } + public Type[] Types { get; } + + public int Arity => this.Types.Length; + public bool Match(Type[] args, bool matchQueryValue) { int n = this.Types.Length - 1; return args.Length >= n && - args.Take(n).Zip(this.Types, (a, t) => a == t || (matchQueryValue && a == typeof(QueryValue))).Aggregate(true, (acc, item) => acc && item) && - args.Skip(n).Select(a => a == this.Types[n] || (matchQueryValue && a == typeof(QueryValue))).Aggregate(true, (acc, item) => acc && item); + args.Take(n).Zip(this.Types, (a, t) => a == t || (matchQueryValue && a == typeof(QueryValue))).Aggregate(true, (acc, item) => acc && item) && + args.Skip(n).Select(a => a == this.Types[n] || (matchQueryValue && a == typeof(QueryValue))).Aggregate(true, (acc, item) => acc && item); } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/CompilationError.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/CompilationError.cs index 20f8ea675c2..5d6796bc697 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/CompilationError.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/CompilationError.cs @@ -5,12 +5,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Errors public class CompilationError { - public string Message { get; } - - public ErrorSeverity Severity { get; } - - public ErrorRange Location { get; } - public CompilationError(ErrorSeverity severity, string message, ErrorRange location) { this.Severity = severity; @@ -18,10 +12,11 @@ public CompilationError(ErrorSeverity severity, string message, ErrorRange locat this.Location = Preconditions.CheckNotNull(location); } - protected bool Equals(CompilationError other) - { - return string.Equals(this.Message, other.Message) && this.Severity == other.Severity && Equals(this.Location, other.Location); - } + public string Message { get; } + + public ErrorSeverity Severity { get; } + + public ErrorRange Location { get; } public override bool Equals(object obj) { @@ -29,10 +24,12 @@ public override bool Equals(object obj) { return false; } + if (ReferenceEquals(this, obj)) { return true; } + return obj.GetType() == this.GetType() && this.Equals((CompilationError)obj); } @@ -47,5 +44,9 @@ public override int GetHashCode() } } + protected bool Equals(CompilationError other) + { + return string.Equals(this.Message, other.Message) && this.Severity == other.Severity && Equals(this.Location, other.Location); + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorPosition.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorPosition.cs index d63e3036438..277300bd0b1 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorPosition.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorPosition.cs @@ -10,16 +10,16 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Errors /// public struct ErrorPosition : IComparable { - public int Line { get; } - - public int Column { get; } - public ErrorPosition(int line, int column) { this.Line = Preconditions.CheckRange(line, 1); this.Column = Preconditions.CheckRange(column, 1); } + public int Line { get; } + + public int Column { get; } + public static bool operator <(ErrorPosition x, ErrorPosition y) { return x.CompareTo(y) < 0; @@ -66,19 +66,13 @@ public int CompareTo(ErrorPosition other) } } - bool Equals(ErrorPosition other) - { - bool lines = this.Line == other.Line; - bool columns = this.Column == other.Column; - return lines && columns; - } - public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } + return obj.GetType() == this.GetType() && this.Equals((ErrorPosition)obj); } @@ -91,5 +85,12 @@ public override int GetHashCode() } public override string ToString() => string.Format(CultureInfo.InvariantCulture, "{0}:{1}", this.Line, this.Column); + + bool Equals(ErrorPosition other) + { + bool lines = this.Line == other.Line; + bool columns = this.Column == other.Column; + return lines && columns; + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorRange.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorRange.cs index b48a956991d..eee0e624616 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorRange.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/errors/ErrorRange.cs @@ -6,22 +6,18 @@ namespace Microsoft.Azure.Devices.Routing.Core.Query.Errors public class ErrorRange { - public ErrorPosition Start { get; } - - public ErrorPosition End { get; } - public ErrorRange(ErrorPosition start, ErrorPosition end) { - Preconditions.CheckArgument(Preconditions.CheckNotNull(start) <= Preconditions.CheckNotNull(end), + Preconditions.CheckArgument( + Preconditions.CheckNotNull(start) <= Preconditions.CheckNotNull(end), string.Format(CultureInfo.InvariantCulture, "Start postition must be less than or equal to end position. Given: {0} and {1}", start, end)); this.Start = start; this.End = end; } - protected bool Equals(ErrorRange other) - { - return this.Start.Equals(other.Start) && this.End.Equals(other.End); - } + public ErrorPosition Start { get; } + + public ErrorPosition End { get; } public override bool Equals(object obj) { @@ -29,10 +25,12 @@ public override bool Equals(object obj) { return false; } + if (ReferenceEquals(this, obj)) { return true; } + return obj.GetType() == this.GetType() && this.Equals((ErrorRange)obj); } @@ -44,5 +42,9 @@ public override int GetHashCode() } } + protected bool Equals(ErrorRange other) + { + return this.Start.Equals(other.Start) && this.End.Equals(other.End); + } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/JsonPathValidator.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/JsonPathValidator.cs index 26ed9207435..5d22d9a2378 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/JsonPathValidator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/JsonPathValidator.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Query.JsonPath { - using static System.FormattableString; using Antlr4.Runtime; using Microsoft.Azure.Devices.Routing.Core.Query.Errors; + using static System.FormattableString; public static class JsonPathValidator { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/TwinChangeJsonPathValidator.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/TwinChangeJsonPathValidator.cs index 6b1125e9786..f1acf632b1d 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/TwinChangeJsonPathValidator.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/jsonpath/TwinChangeJsonPathValidator.cs @@ -16,12 +16,18 @@ static class TwinChangeJsonPathValidator static readonly Lazy ErrorMessageFormat = new Lazy( () => - string.Format(CultureInfo.InvariantCulture, "{0}{1}.", + string.Format( + CultureInfo.InvariantCulture, + "{0}{1}.", "'{0}' is not supported. Supported prefixes: ", - TwinChangeJsonSupportedPrefixes.Select((s) => - string.Format(CultureInfo.InvariantCulture, "'{0}'", s)).Aggregate( - (s1, s2) => string.Format(CultureInfo.InvariantCulture, "{0}, {1}", - s1, s2)))); + TwinChangeJsonSupportedPrefixes.Select( + (s) => + string.Format(CultureInfo.InvariantCulture, "'{0}'", s)).Aggregate( + (s1, s2) => string.Format( + CultureInfo.InvariantCulture, + "{0}, {1}", + s1, + s2)))); public static bool IsSupportedJsonPath(string jsonPath, out string errorDetails) { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValue.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValue.cs index 92803b49298..a1cb3c259ad 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValue.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValue.cs @@ -12,16 +12,16 @@ public class QueryValue : IComparable, IComparable public static readonly QueryValue Null = new QueryValue(Query.Null.Instance, QueryValueType.Null); - public QueryValueType ValueType { get; set; } - - public object Value { get; set; } - public QueryValue(object value, QueryValueType valueType) { this.Value = value; this.ValueType = valueType; } + public QueryValueType ValueType { get; set; } + + public object Value { get; set; } + public static QueryValue Create(JValue jsonValue) { switch (jsonValue.Type) @@ -176,7 +176,7 @@ public static explicit operator Bool(QueryValue x) return Undefined; } - // Used for logical operators with Bool e.g. ||) + // Used for logical operators with Bool e.g. ||) public static QueryValue operator |(QueryValue x, QueryValue y) { if (x.ValueType == QueryValueType.Bool && y.ValueType == QueryValueType.Bool) @@ -193,6 +193,14 @@ public static explicit operator Bool(QueryValue x) // Used for logical operators with Bool public static bool operator false(QueryValue x) => x.ValueType == QueryValueType.Bool && (Bool)x.Value; + public static bool IsSupportedType(Type t) + { + return t == typeof(Null) || + t == typeof(double) || + t == typeof(string) || + t == typeof(Bool); + } + public int CompareTo(object obj) { return this.CompareTo(obj as QueryValue); @@ -219,8 +227,7 @@ static int Compare(QueryValue value1, QueryValue value2) // ReSharper disable once PossibleNullReferenceException if (value1.ValueType == QueryValueType.None || value2.ValueType == QueryValueType.None) { - return ReferenceEquals(value1, value2) ? -1 : - value1.GetHashCode().CompareTo(value2.GetHashCode()); + return ReferenceEquals(value1, value2) ? -1 : value1.GetHashCode().CompareTo(value2.GetHashCode()); } if (value1.ValueType != value2.ValueType) @@ -252,20 +259,11 @@ static int Compare(QueryValue value1, QueryValue value2) case QueryValueType.Object: // We do not support value based comparisons on object. Just return based on reference equals. - return ReferenceEquals(value1.Value, value2.Value) ? 0 : - value1.Value.GetHashCode().CompareTo(value2.Value.GetHashCode()); + return ReferenceEquals(value1.Value, value2.Value) ? 0 : value1.Value.GetHashCode().CompareTo(value2.Value.GetHashCode()); default: throw new ArgumentOutOfRangeException(); } } - - public static bool IsSupportedType(Type t) - { - return t == typeof(Null) || - t == typeof(double) || - t == typeof(string) || - t == typeof(Bool); - } } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValueType.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValueType.cs index 359f34442e2..a16b0abd5da 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValueType.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/query/types/QueryValueType.cs @@ -8,8 +8,8 @@ public enum QueryValueType Double, String, Null, - Object - //DateTime, - //Timespan, + Object, + // DateTime, + // Timespan, } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FilteringRoutingService.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FilteringRoutingService.cs index c3213b9b85c..702644bff81 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FilteringRoutingService.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FilteringRoutingService.cs @@ -28,10 +28,6 @@ public class FilteringRoutingService : IRoutingService readonly AsyncLock sync; readonly IRoutingService underlying; - ImmutableDictionary Evaluators => this.evaluators; - - ImmutableDictionary Notifiers => this.notifiers; - public FilteringRoutingService(IRoutingService underlying, IRouteStore routeStore, INotifierFactory notifierFactory) : this(underlying, routeStore, notifierFactory, RouteCompiler.Instance) { @@ -51,6 +47,10 @@ public FilteringRoutingService(IRoutingService underlying, IRouteStore routeStor this.sync = new AsyncLock(); } + ImmutableDictionary Evaluators => this.evaluators; + + ImmutableDictionary Notifiers => this.notifiers; + public Task RouteAsync(string hubName, IMessage message) => this.RouteAsync(hubName, new[] { message }); public async Task RouteAsync(string hubName, IEnumerable messages) @@ -89,7 +89,6 @@ public async Task CloseAsync(CancellationToken token) protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.closed); if (disposing) { this.cts.Dispose(); @@ -102,23 +101,35 @@ protected virtual void Dispose(bool disposing) } } - void CheckClosed() + static async Task CloseEvaluatorAsync(Evaluator evaluator, CancellationToken token) { - if (this.closed) + try { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} is closed.", nameof(FilteringRoutingService))); + await evaluator.CloseAsync(token); + } + catch (Exception ex) + { + Events.EvaluatorCloseFailed(ex); } } - static async Task CloseEvaluatorAsync(Evaluator evaluator, CancellationToken token) + static async Task CloseNotifierAsync(INotifier notifier, CancellationToken token) { try { - await evaluator.CloseAsync(token); + await notifier.CloseAsync(token); } catch (Exception ex) { - Events.EvaluatorCloseFailed(ex); + Events.NotifierCloseFailed(ex); + } + } + + void CheckClosed() + { + if (this.closed) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} is closed.", nameof(FilteringRoutingService))); } } @@ -151,6 +162,7 @@ async Task GetEvaluatorAsync(string hubName) } } } + return evaluator; } @@ -231,22 +243,10 @@ async Task RemoveEvaluatorAsync(string hubName) } } - static async Task CloseNotifierAsync(INotifier notifier, CancellationToken token) - { - try - { - await notifier.CloseAsync(token); - } - catch (Exception ex) - { - Events.NotifierCloseFailed(ex); - } - } - static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.FilteringRoutingService; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FrontendRoutingService.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FrontendRoutingService.cs index ebad322e2cc..fbb1f6158e9 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FrontendRoutingService.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/services/FrontendRoutingService.cs @@ -9,7 +9,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Services using System.Linq; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Extensions.Logging; @@ -26,10 +25,6 @@ public class FrontendRoutingService : IRoutingService readonly AtomicReference>> sinks; readonly AsyncLock sync = new AsyncLock(); - ImmutableDictionary Notifiers => this.notifiers; - - ImmutableDictionary> Sinks => this.sinks; - public FrontendRoutingService(ISinkFactory sinkFactory, INotifierFactory notifierFactory) { this.sinkFactory = Preconditions.CheckNotNull(sinkFactory, nameof(sinkFactory)); @@ -40,6 +35,10 @@ public FrontendRoutingService(ISinkFactory sinkFactory, INotifierFacto this.notifiers = new AtomicReference>(ImmutableDictionary.Empty); } + ImmutableDictionary Notifiers => this.notifiers; + + ImmutableDictionary> Sinks => this.sinks; + public Task RouteAsync(string hubName, IMessage message) => this.RouteAsync(hubName, new[] { message }); public async Task RouteAsync(string hubName, IEnumerable messages) @@ -85,7 +84,6 @@ public async Task CloseAsync(CancellationToken token) protected virtual void Dispose(bool disposing) { - //Debug.Assert(this.closed); if (disposing) { this.cts.Dispose(); @@ -98,6 +96,30 @@ protected virtual void Dispose(bool disposing) } } + static async Task CloseSinkAsync(ISink sink, CancellationToken token) + { + try + { + await sink.CloseAsync(token); + } + catch (Exception ex) + { + Events.SinkCloseFailed(ex); + } + } + + static async Task CloseNotifierAsync(INotifier notifier, CancellationToken token) + { + try + { + await notifier.CloseAsync(token); + } + catch (Exception ex) + { + Events.NotifierCloseFailed(ex); + } + } + async Task> GetSinkAsync(string hubName) { ISink sink; @@ -126,6 +148,7 @@ async Task> GetSinkAsync(string hubName) } } } + return sink; } @@ -187,34 +210,10 @@ void CheckClosed() } } - static async Task CloseSinkAsync(ISink sink, CancellationToken token) - { - try - { - await sink.CloseAsync(token); - } - catch (Exception ex) - { - Events.SinkCloseFailed(ex); - } - } - - static async Task CloseNotifierAsync(INotifier notifier, CancellationToken token) - { - try - { - await notifier.CloseAsync(token); - } - catch (Exception ex) - { - Events.NotifierCloseFailed(ex); - } - } - static class Events { - static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); const int IdStart = Routing.EventIds.FrontendRoutingService; + static readonly ILogger Log = Routing.LoggerFactory.CreateLogger(); enum EventIds { diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/sinks/RetryingSink.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/sinks/RetryingSink.cs index 65cb8d411fe..ada4a866c16 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/sinks/RetryingSink.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/sinks/RetryingSink.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Sinks using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; - using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Azure.Devices.Routing.Core.Util; public class RetryingSink : ISink @@ -72,6 +71,7 @@ public async Task> ProcessAsync(ICollection ts, CancellationTo } } } + rv = new SinkResult(succeeded, failed, invalid, failureDetails); } catch (OperationCanceledException ex) @@ -84,6 +84,7 @@ public async Task> ProcessAsync(ICollection ts, CancellationTo failed.AddRange(messages); rv = new SinkResult(succeeded, failed, invalid, new SendFailureDetails(FailureKind.InternalError, ex)); } + return rv; } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/CollectionEx.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/CollectionEx.cs index 0107da7ef75..b4d3a426c22 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/CollectionEx.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/CollectionEx.cs @@ -35,6 +35,7 @@ public static Option HeadOption(this IEnumerable src) } } } + return Option.None(); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Option.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Option.cs index b6c4c48eb51..f1f334e57f8 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Option.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Option.cs @@ -8,16 +8,20 @@ namespace Microsoft.Azure.Devices.Routing.Core.Util public struct Option : IEquatable> { - public bool HasValue { get; } - - T Value { get; } - internal Option(T value, bool hasValue) { this.Value = value; this.HasValue = hasValue; } + public bool HasValue { get; } + + T Value { get; } + + public static bool operator ==(Option opt1, Option opt2) => opt1.Equals(opt2); + + public static bool operator !=(Option opt1, Option opt2) => !opt1.Equals(opt2); + public bool Equals(Option other) { if (!this.HasValue && !other.HasValue) @@ -28,21 +32,19 @@ public bool Equals(Option other) { return EqualityComparer.Default.Equals(this.Value, other.Value); } + return false; } public override bool Equals(object obj) => obj is Option && this.Equals((Option)obj); - public static bool operator ==(Option opt1, Option opt2) => opt1.Equals(opt2); - - public static bool operator !=(Option opt1, Option opt2) => !opt1.Equals(opt2); - public override int GetHashCode() { if (this.HasValue) { return this.Value == null ? 1 : this.Value.GetHashCode(); } + return 0; } @@ -71,6 +73,7 @@ public bool Contains(T value) { return this.Value == null ? value == null : this.Value.Equals(value); } + return false; } @@ -96,16 +99,14 @@ public Option Map(Func mapping) { return this.Match( some: value => Option.Some(mapping(value)), - none: Option.None - ); + none: Option.None); } public Option FlatMap(Func> mapping) { return this.Match( some: mapping, - none: Option.None - ); + none: Option.None); } [Pure] @@ -114,8 +115,7 @@ public Option Filter(Func predicate) Option original = this; return this.Match( some: value => predicate(value) ? original : Option.None(), - none: () => original - ); + none: () => original); } } diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Preconditions.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Preconditions.cs index 89853489162..02eb72cfcbd 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Preconditions.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/Preconditions.cs @@ -11,7 +11,7 @@ class Preconditions /// /// /// The reference - public static T CheckNotNull(T reference) => CheckNotNull(reference, "", ""); + public static T CheckNotNull(T reference) => CheckNotNull(reference, string.Empty, string.Empty); /// /// Checks that a reference isn't null. Throws ArgumentNullException if null. @@ -20,7 +20,7 @@ class Preconditions /// /// /// The reference - public static T CheckNotNull(T reference, string paramName) => CheckNotNull(reference, paramName, ""); + public static T CheckNotNull(T reference, string paramName) => CheckNotNull(reference, paramName, string.Empty); /// /// Checks that a reference isn't null. Throws ArgumentNullException if null. @@ -43,6 +43,7 @@ public static T CheckNotNull(T reference, string paramName, string message) throw string.IsNullOrEmpty(message) ? new ArgumentNullException(paramName) : new ArgumentNullException(paramName, message); } } + return reference; } @@ -78,7 +79,8 @@ public static void CheckArgument(bool expression, string message) /// Item to check. /// Inclusive low value. /// - public static T CheckRange(T item, T low) where T : IComparable => + public static T CheckRange(T item, T low) + where T : IComparable => CheckRange(item, low, nameof(item)); /// @@ -89,8 +91,9 @@ public static T CheckRange(T item, T low) where T : IComparable => /// Inclusive low value. /// /// - public static T CheckRange(T item, T low, string paramName) where T : IComparable => - CheckRange(item, low, paramName, ""); + public static T CheckRange(T item, T low, string paramName) + where T : IComparable => + CheckRange(item, low, paramName, string.Empty); /// /// This checks that the item is greater than or equal to the low value. @@ -101,12 +104,14 @@ public static T CheckRange(T item, T low, string paramName) where T : ICompar /// /// /// - public static T CheckRange(T item, T low, string paramName, string message) where T : IComparable + public static T CheckRange(T item, T low, string paramName, string message) + where T : IComparable { if (item.CompareTo(low) < 0) { throw new ArgumentOutOfRangeException(paramName, item, message); } + return item; } @@ -119,7 +124,8 @@ public static T CheckRange(T item, T low, string paramName, string message) w /// Inclusive low value. /// Exclusive high value /// - public static T CheckRange(T item, T low, T high) where T : IComparable => + public static T CheckRange(T item, T low, T high) + where T : IComparable => CheckRange(item, low, high, nameof(item)); /// @@ -132,8 +138,9 @@ public static T CheckRange(T item, T low, T high) where T : IComparable => /// Exclusive high value /// /// - public static T CheckRange(T item, T low, T high, string paramName) where T : IComparable => - CheckRange(item, low, high, paramName, ""); + public static T CheckRange(T item, T low, T high, string paramName) + where T : IComparable => + CheckRange(item, low, high, paramName, string.Empty); /// /// This checks that the item is in the range [low, high). @@ -146,13 +153,15 @@ public static T CheckRange(T item, T low, T high, string paramName) where T : /// /// /// - public static T CheckRange(T item, T low, T high, string paramName, string message) where T : IComparable + public static T CheckRange(T item, T low, T high, string paramName, string message) + where T : IComparable { if (item.CompareTo(low) < 0 || item.CompareTo(high) >= 0) { throw new ArgumentOutOfRangeException(paramName, item, message); } + return item; } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AsyncLock.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AsyncLock.cs index 9f6f05398bc..14c19f1bfff 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AsyncLock.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AsyncLock.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Util.Concurrency { - // // Code ported from http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx - // using System; using System.Threading; using System.Threading.Tasks; @@ -13,7 +11,8 @@ public sealed class AsyncLock : IDisposable readonly Task releaser; readonly SemaphoreSlim semaphore; - public AsyncLock() : this(1) + public AsyncLock() + : this(1) { } @@ -28,10 +27,14 @@ public AsyncLock(int maximumConcurrency) public Task LockAsync(CancellationToken token) { Task wait = this.semaphore.WaitAsync(token); - return wait.Status == TaskStatus.RanToCompletion ? this.releaser : - wait.ContinueWith((_, state) => new Releaser((AsyncLock)state), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + return wait.Status == TaskStatus.RanToCompletion + ? this.releaser + : wait.ContinueWith( + (_, state) => new Releaser((AsyncLock)state), + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, + TaskScheduler.Default); } /// diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicBoolean.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicBoolean.cs index ea94a664ad0..8a33f1a434e 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicBoolean.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicBoolean.cs @@ -12,7 +12,15 @@ public AtomicBoolean(bool value) this.underlying = value ? 1 : 0; } - public AtomicBoolean() : this(false) { } + public AtomicBoolean() + : this(false) + { + } + + public static implicit operator bool(AtomicBoolean value) + { + return value.Get(); + } public bool Get() { @@ -35,10 +43,5 @@ public bool CompareAndSet(bool expected, bool result) int r = result ? 1 : 0; return Interlocked.CompareExchange(ref this.underlying, r, e) == e; } - - public static implicit operator bool(AtomicBoolean value) - { - return value.Get(); - } } -} \ No newline at end of file +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicReference.cs b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicReference.cs index dd8d2e31504..50c6228f49f 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicReference.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Routing.Core/util/concurrency/AtomicReference.cs @@ -3,7 +3,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Util.Concurrency { using System.Threading; - public class AtomicReference where T : class + public class AtomicReference + where T : class { T val; @@ -12,6 +13,11 @@ public AtomicReference(T value) this.val = value; } + public static implicit operator T(AtomicReference reference) + { + return reference.Get(); + } + public bool CompareAndSet(T expect, T update) { return Interlocked.CompareExchange(ref this.val, update, expect) == expect; @@ -26,10 +32,5 @@ public T Get() { return this.val; } - - public static implicit operator T(AtomicReference reference) - { - return reference.Get(); - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpDirectMethodMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpDirectMethodMessageConverterTest.cs index 3d91798a343..374d97f24f4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpDirectMethodMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpDirectMethodMessageConverterTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Amqp.Constants; [Unit] public class AmqpDirectMethodMessageConverterTest @@ -20,14 +21,16 @@ public void FromMessageTest() string correlationId = Guid.NewGuid().ToString(); var data = new byte[] { 0, 1, 2 }; IMessage message = new EdgeMessage.Builder(data) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = correlationId - }) - .SetProperties(new Dictionary - { - [Amqp.Constants.MessagePropertiesMethodNameKey] = inputName - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = correlationId + }) + .SetProperties( + new Dictionary + { + [Constants.MessagePropertiesMethodNameKey] = inputName + }) .Build(); IMessageConverter messageConverter = new AmqpDirectMethodMessageConverter(); @@ -38,7 +41,7 @@ public void FromMessageTest() Assert.NotNull(amqpMessage); Assert.Equal(data, amqpMessage.GetPayloadBytes()); Assert.Equal(correlationId, amqpMessage.Properties.CorrelationId.ToString()); - Assert.Equal(inputName, amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesMethodNameKey]); + Assert.Equal(inputName, amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesMethodNameKey]); } [Fact] @@ -49,7 +52,7 @@ public void ToMessageTest() var data = new byte[] { 0, 1, 2 }; AmqpMessage amqpMessage = AmqpMessage.Create(new Data { Value = new ArraySegment(data) }); amqpMessage.Properties.CorrelationId = correlationId; - amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesStatusKey] = 200; + amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesStatusKey] = 200; IMessageConverter messageConverter = new AmqpDirectMethodMessageConverter(); // Act diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpMessageConverterTest.cs index fcaefd8afc5..5753cb42802 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpMessageConverterTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Amqp.Constants; [Unit] public class AmqpMessageConverterTest @@ -117,15 +118,15 @@ public void ToMessageTest_AllProperties() amqpMessage.Properties.UserId = new ArraySegment(Encoding.UTF8.GetBytes(userId)); amqpMessage.Properties.AbsoluteExpiryTime = expiryTime; - amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsEnqueuedTimeKey] = enqueuedTime; - amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsDeliveryCountKey] = deliveryCount; - amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsLockTokenName] = lockToken; - amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsSequenceNumberName] = sequenceNumber; + amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsEnqueuedTimeKey] = enqueuedTime; + amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsDeliveryCountKey] = deliveryCount; + amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsLockTokenName] = lockToken; + amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsSequenceNumberName] = sequenceNumber; - amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesMessageSchemaKey] = messageSchema; - amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesCreationTimeKey] = creationTime; - amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesOperationKey] = operation; - amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesOutputNameKey] = outputName; + amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesMessageSchemaKey] = messageSchema; + amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesCreationTimeKey] = creationTime; + amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesOperationKey] = operation; + amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesOutputNameKey] = outputName; amqpMessage.ApplicationProperties.Map["Prop1"] = "Value1"; amqpMessage.ApplicationProperties.Map["Prop2"] = "Value2"; @@ -292,18 +293,18 @@ byte[] GetMessageBody(AmqpMessage sourceMessage) Assert.Equal(userId, Encoding.UTF8.GetString(amqpMessage.Properties.UserId.Array)); Assert.Equal(expiryTime, amqpMessage.Properties.AbsoluteExpiryTime.HasValue ? amqpMessage.Properties.AbsoluteExpiryTime.Value : DateTime.MinValue); - Assert.Equal(enqueuedTime, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsEnqueuedTimeKey]); - Assert.Equal(deliveryCount, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsDeliveryCountKey]); - Assert.Equal(lockToken, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsLockTokenName]); - Assert.Equal(sequenceNumber, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsSequenceNumberName]); - Assert.Equal(inputName, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsInputNameKey]); - Assert.Equal(connectionDeviceId, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsConnectionDeviceId]); - Assert.Equal(connectionModuleId, amqpMessage.MessageAnnotations.Map[Amqp.Constants.MessageAnnotationsConnectionModuleId]); - - Assert.Equal(messageSchema, amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesMessageSchemaKey]); - Assert.Equal(creationTime, amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesCreationTimeKey]); - Assert.Equal(operation, amqpMessage.ApplicationProperties.Map[Amqp.Constants.MessagePropertiesOperationKey]); - Assert.False(amqpMessage.ApplicationProperties.Map.TryGetValue(Amqp.Constants.MessagePropertiesOutputNameKey, out string _)); + Assert.Equal(enqueuedTime, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsEnqueuedTimeKey]); + Assert.Equal(deliveryCount, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsDeliveryCountKey]); + Assert.Equal(lockToken, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsLockTokenName]); + Assert.Equal(sequenceNumber, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsSequenceNumberName]); + Assert.Equal(inputName, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsInputNameKey]); + Assert.Equal(connectionDeviceId, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsConnectionDeviceId]); + Assert.Equal(connectionModuleId, amqpMessage.MessageAnnotations.Map[Constants.MessageAnnotationsConnectionModuleId]); + + Assert.Equal(messageSchema, amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesMessageSchemaKey]); + Assert.Equal(creationTime, amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesCreationTimeKey]); + Assert.Equal(operation, amqpMessage.ApplicationProperties.Map[Constants.MessagePropertiesOperationKey]); + Assert.False(amqpMessage.ApplicationProperties.Map.TryGetValue(Constants.MessagePropertiesOutputNameKey, out string _)); Assert.Equal("Value1", amqpMessage.ApplicationProperties.Map["Prop1"].ToString()); Assert.Equal("Value2", amqpMessage.ApplicationProperties.Map["Prop2"].ToString()); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpProtocolHeadTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpProtocolHeadTest.cs index 335bd7d6fce..dc49af30d8f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpProtocolHeadTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpProtocolHeadTest.cs @@ -30,7 +30,7 @@ public void TestInvalidConstructorInputs() const bool clientCertsAllowed = true; X509Certificate2 tlsCertificate = CertificateHelper.GenerateSelfSignedCert("TestCert"); var transportSettings = new DefaultTransportSettings(Scheme, HostName, Port, tlsCertificate, clientCertsAllowed, Mock.Of(), Mock.Of()); - AmqpSettings amqpSettings = AmqpSettingsProvider.GetDefaultAmqpSettings(IotHubHostName, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new NullCredentialsCache()); + AmqpSettings amqpSettings = AmqpSettingsProvider.GetDefaultAmqpSettings(IotHubHostName, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new NullCredentialsCache()); var transportListenerProvider = new Mock(); var webSockerListenerRegistry = new Mock(); @@ -72,24 +72,15 @@ public async void TestStartAsyncThrowsIfRootCreateTransportListenerThrows() transportSettings.SetupGet(sp => sp.Settings).Returns(amqpTransportSettings.Object); var transportListenerProvider = new Mock(); - transportListenerProvider.Setup(tlp => tlp.Create( - It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), - amqpSettings - )).Throws(new ApplicationException("No donuts for you")); + transportListenerProvider.Setup( + tlp => tlp.Create( + It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), + amqpSettings)).Throws(new ApplicationException("No donuts for you")); var protocolHead = new AmqpProtocolHead(transportSettings.Object, amqpSettings, transportListenerProvider.Object, Mock.Of(), Mock.Of(), Mock.Of()); await Assert.ThrowsAsync(() => protocolHead.StartAsync()); } - static IEnumerable GetThrowingListeners() => new[] - { - // Causes OpenAsync to throw - new[] { new ThrowingTransportListener("AMQP", new ApplicationException("No donuts for you"), null) }, - - // Causes Listen to throw - new[] { new ThrowingTransportListener("AMQP", null, new ApplicationException("No donuts for you")) } - }; - [Theory] [MemberData(nameof(GetThrowingListeners))] [Unit] @@ -104,10 +95,10 @@ public async void TestStartAsyncThrowsIfOpenAsyncOrListenThrows(TransportListene transportSettings.SetupGet(sp => sp.Settings).Returns(amqpTransportSettings.Object); var transportListenerProvider = new Mock(); - transportListenerProvider.Setup(tlp => tlp.Create( - It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), - amqpSettings - )).Returns(amqpTransportListener); + transportListenerProvider.Setup( + tlp => tlp.Create( + It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), + amqpSettings)).Returns(amqpTransportListener); var protocolHead = new AmqpProtocolHead(transportSettings.Object, amqpSettings, transportListenerProvider.Object, Mock.Of(), Mock.Of(), Mock.Of()); await Assert.ThrowsAsync(() => protocolHead.StartAsync()); @@ -134,10 +125,10 @@ public async void TestStartAsyncThrowsIfTransportListenerCallbackArgsHasExceptio }); var transportListenerProvider = new Mock(); - transportListenerProvider.Setup(tlp => tlp.Create( - It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), - amqpSettings - )).Returns(amqpTransportListener); + transportListenerProvider.Setup( + tlp => tlp.Create( + It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), + amqpSettings)).Returns(amqpTransportListener); var protocolHead = new AmqpProtocolHead(transportSettings.Object, amqpSettings, transportListenerProvider.Object, Mock.Of(), Mock.Of(), Mock.Of()); await Assert.ThrowsAsync(() => protocolHead.StartAsync()); @@ -168,10 +159,10 @@ public async void TestStartAsyncDoesNotThrowIfCreateConnectionThrows() }); var transportListenerProvider = new Mock(); - transportListenerProvider.Setup(tlp => tlp.Create( - It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), - amqpSettings - )).Returns(amqpTransportListener); + transportListenerProvider.Setup( + tlp => tlp.Create( + It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), + amqpSettings)).Returns(amqpTransportListener); var protocolHead = new AmqpProtocolHead(transportSettings.Object, amqpSettings, transportListenerProvider.Object, Mock.Of(), Mock.Of(), Mock.Of()); await protocolHead.StartAsync(); @@ -189,16 +180,17 @@ public async void TestStartAsyncDoesNotThrowIfConnectionOpenAsyncThrows() TestHelperAmqpConnection amqpConnection = null; runtimeProvider.Setup(rp => rp.CreateConnection(tcpTransport.Object, It.IsAny(), false, It.IsAny(), It.IsAny())) - .Callback(( - TransportBase transport, - ProtocolHeader protocolHeader, - bool isInitiator, - AmqpSettings settings, - AmqpConnectionSettings connectionSettings) => - { - amqpConnection = new TestHelperAmqpConnection(transport, protocolHeader, isInitiator, settings, connectionSettings); - amqpConnection.OnOpenInternal = () => throw new OperationCanceledException("No donuts for you"); - }) + .Callback( + ( + TransportBase transport, + ProtocolHeader protocolHeader, + bool isInitiator, + AmqpSettings settings, + AmqpConnectionSettings connectionSettings) => + { + amqpConnection = new TestHelperAmqpConnection(transport, protocolHeader, isInitiator, settings, connectionSettings); + amqpConnection.OnOpenInternal = () => throw new OperationCanceledException("No donuts for you"); + }) .Returns(() => amqpConnection); var tcpTransportListener = new Mock("TCP"); @@ -216,10 +208,10 @@ public async void TestStartAsyncDoesNotThrowIfConnectionOpenAsyncThrows() }); var transportListenerProvider = new Mock(); - transportListenerProvider.Setup(tlp => tlp.Create( - It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), - amqpSettings - )).Returns(amqpTransportListener); + transportListenerProvider.Setup( + tlp => tlp.Create( + It.Is>(listeners => listeners.Contains(tcpTransportListener.Object)), + amqpSettings)).Returns(amqpTransportListener); var protocolHead = new AmqpProtocolHead(transportSettings.Object, amqpSettings, transportListenerProvider.Object, Mock.Of(), Mock.Of(), Mock.Of()); await protocolHead.StartAsync(); @@ -229,17 +221,32 @@ public async void TestStartAsyncDoesNotThrowIfConnectionOpenAsyncThrows() Assert.True(amqpConnection.WasClosed); } - class TestHelperAmqpConnection : AmqpConnection + static IEnumerable GetThrowingListeners() => new[] { - public Action OnOpenInternal { get; set; } - public bool WasClosed { get; set; } + // Causes OpenAsync to throw + new[] { new ThrowingTransportListener("AMQP", new ApplicationException("No donuts for you"), null) }, + + // Causes Listen to throw + new[] { new ThrowingTransportListener("AMQP", null, new ApplicationException("No donuts for you")) } + }; - public TestHelperAmqpConnection(TransportBase transport, ProtocolHeader protocolHeader, bool - isInitiator, AmqpSettings amqpSettings, AmqpConnectionSettings connectionSettings) + class TestHelperAmqpConnection : AmqpConnection + { + public TestHelperAmqpConnection( + TransportBase transport, + ProtocolHeader protocolHeader, + bool + isInitiator, + AmqpSettings amqpSettings, + AmqpConnectionSettings connectionSettings) : base(transport, protocolHeader, isInitiator, amqpSettings, connectionSettings) { } + public Action OnOpenInternal { get; set; } + + public bool WasClosed { get; set; } + protected override bool OpenInternal() { this.OnOpenInternal?.Invoke(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpSettingsProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpSettingsProviderTest.cs index 1975ae65c91..d57de5ab700 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpSettingsProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpSettingsProviderTest.cs @@ -12,11 +12,12 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Amqp.Constants; [Unit] public class AmqpSettingsProviderTest { - [Fact] + [Fact] public void TestInvalidInputsForGetDefaultAmqpSettings() { const string IotHubHostName = "restaurantatendofuniverse.azure-devices.net"; @@ -58,13 +59,13 @@ public void ValidateSettingsTest() SaslHandler plainHandler = saslTransportProvider.GetHandler("PLAIN", false); Assert.NotNull(plainHandler); - SaslHandler cbsHandler = saslTransportProvider.GetHandler(Amqp.Constants.ServiceBusCbsSaslMechanismName, false); + SaslHandler cbsHandler = saslTransportProvider.GetHandler(Constants.ServiceBusCbsSaslMechanismName, false); Assert.NotNull(cbsHandler); var amqpTransportProvider = settings.GetTransportProvider(); Assert.NotNull(amqpTransportProvider); - Assert.Equal(Amqp.Constants.AmqpVersion100, amqpTransportProvider.Versions[0]); + Assert.Equal(Constants.AmqpVersion100, amqpTransportProvider.Versions[0]); } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpTwinMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpTwinMessageConverterTest.cs index cbd05a79118..ecd8cb0aef6 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpTwinMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/AmqpTwinMessageConverterTest.cs @@ -26,10 +26,11 @@ public void FromMessageTest() string correlationId = Guid.NewGuid().ToString(); byte[] data = Encoding.UTF8.GetBytes(collection.ToJson()); IMessage message = new EdgeMessage.Builder(data) - .SetSystemProperties(new Dictionary - { - [SystemProperties.CorrelationId] = correlationId - }) + .SetSystemProperties( + new Dictionary + { + [SystemProperties.CorrelationId] = correlationId + }) .Build(); IMessageConverter messageConverter = new AmqpTwinMessageConverter(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/CbsNodeTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/CbsNodeTest.cs index 8a2c6af1395..8888bf10708 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/CbsNodeTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/CbsNodeTest.cs @@ -64,7 +64,7 @@ public void ValidateTestThrowsOnInvalidAudience() }; AmqpMessage invalidAmqpMessage2 = AmqpMessage.Create(amqpValue); invalidAmqpMessage2.ApplicationProperties.Map[CbsConstants.PutToken.Type] = "azure-devices.net:sastoken"; - invalidAmqpMessage2.ApplicationProperties.Map[CbsConstants.PutToken.Audience] = ""; + invalidAmqpMessage2.ApplicationProperties.Map[CbsConstants.PutToken.Audience] = string.Empty; invalidAmqpMessage2.ApplicationProperties.Map[CbsConstants.Operation] = CbsConstants.PutToken.OperationValue; // Act/Assert Assert.Throws(() => CbsNode.ValidateAndParseMessage(IoTHubHostName, invalidAmqpMessage2)); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ConnectionHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ConnectionHandlerTest.cs index 77dcdf320a2..edee1f047e1 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ConnectionHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ConnectionHandlerTest.cs @@ -12,6 +12,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Amqp.Constants; [Unit] public class ConnectionHandlerTest @@ -48,6 +49,7 @@ public async Task GetDeviceListenerTest() { tasks.Add(connectionHandler.GetDeviceListener()); } + IList deviceListeners = (await Task.WhenAll(tasks)).ToList(); // Assert @@ -57,11 +59,12 @@ public async Task GetDeviceListenerTest() { Assert.Equal(deviceListener, deviceListeners[0]); } + Assert.NotNull(deviceProxy); Mock.Get(connectionProvider).Verify(c => c.GetDeviceListenerAsync(It.IsAny()), Times.Once); Mock.Get(deviceListener).Verify(d => d.BindDeviceProxy(It.IsAny()), Times.Once); } - + [Fact] public async Task RegisterC2DMessageSenderTest() { @@ -73,7 +76,7 @@ public async Task RegisterC2DMessageSenderTest() .Callback(d => deviceProxy = d); var connectionProvider = Mock.Of(c => c.GetDeviceListenerAsync(identity) == Task.FromResult(deviceListener)); - + var connectionHandler = new ClientConnectionHandler(identity, connectionProvider); IMessage receivedMessage = null; @@ -167,7 +170,7 @@ public async Task RegisterMethodInvokerTest() Assert.NotNull(receivedMessage); Assert.Equal(sentRequest.Data, receivedMessage.Body); Assert.Equal(sentRequest.CorrelationId, receivedMessage.SystemProperties[SystemProperties.CorrelationId]); - Assert.Equal(sentRequest.Name, receivedMessage.Properties[Amqp.Constants.MessagePropertiesMethodNameKey]); + Assert.Equal(sentRequest.Name, receivedMessage.Properties[Constants.MessagePropertiesMethodNameKey]); } [Fact] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DefaultTransportSettingsTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DefaultTransportSettingsTest.cs index 13733076318..25e3edbf8e4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DefaultTransportSettingsTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DefaultTransportSettingsTest.cs @@ -7,8 +7,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; using Moq; + using Xunit; public class DefaultTransportSettingsTest { @@ -25,10 +25,10 @@ public void TestInvalidConstructorInputs() var autheticator = Mock.Of(); Assert.Throws(() => new DefaultTransportSettings(null, HostName, Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); - Assert.Throws(() => new DefaultTransportSettings("", HostName, Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); + Assert.Throws(() => new DefaultTransportSettings(string.Empty, HostName, Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); Assert.Throws(() => new DefaultTransportSettings(" ", HostName, Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); Assert.Throws(() => new DefaultTransportSettings(Scheme, null, Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); - Assert.Throws(() => new DefaultTransportSettings(Scheme, "", Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); + Assert.Throws(() => new DefaultTransportSettings(Scheme, string.Empty, Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); Assert.Throws(() => new DefaultTransportSettings(Scheme, " ", Port, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); Assert.Throws(() => new DefaultTransportSettings(Scheme, HostName, -1, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); Assert.Throws(() => new DefaultTransportSettings(Scheme, HostName, 70000, tlsCertificate, clientCertsAllowed, autheticator, credentialsProvider)); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DeviceBoundLinkHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DeviceBoundLinkHandlerTest.cs index 1eb271dcdd4..38a9adde50d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DeviceBoundLinkHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/DeviceBoundLinkHandlerTest.cs @@ -92,7 +92,7 @@ public async Task SendMessageTest() var sendingLinkHandler = new DeviceBoundLinkHandler(identity, sendingLink, requestUri, boundVariables, connectionHandler, messageConverter); var body = new byte[] { 0, 1, 2, 3 }; IMessage message = new EdgeMessage.Builder(body).Build(); - var deliveryState = new Mock(new AmqpSymbol(""), AmqpConstants.AcceptedOutcome.DescriptorCode); + var deliveryState = new Mock(new AmqpSymbol(string.Empty), AmqpConstants.AcceptedOutcome.DescriptorCode); var delivery = Mock.Of( d => d.State == deliveryState.Object && d.DeliveryTag == new ArraySegment(Guid.NewGuid().ToByteArray())); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EdgeX509PrincipalTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EdgeX509PrincipalTest.cs index 1f80626dd49..0a7b743db8e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EdgeX509PrincipalTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EdgeX509PrincipalTest.cs @@ -2,17 +2,16 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test { using System; + using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; - using Microsoft.Azure.Amqp.Transport; + using System.Threading.Tasks; using Microsoft.Azure.Amqp.X509; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; using Moq; using Xunit; - using System.Collections.Generic; - using System.Threading.Tasks; + using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; [Unit] public class EdgeX509PrincipalTest @@ -54,7 +53,7 @@ public async Task TestAuthenticateAsyncWithInvalidId_FailsAsync() var principal = new EdgeX509Principal(identity, chain, auth, cf); await Assert.ThrowsAsync(() => principal.AuthenticateAsync(null)); - await Assert.ThrowsAsync(() => principal.AuthenticateAsync("")); + await Assert.ThrowsAsync(() => principal.AuthenticateAsync(string.Empty)); await Assert.ThrowsAsync(() => principal.AuthenticateAsync(" ")); Assert.False(await principal.AuthenticateAsync("/ ")); Assert.False(await principal.AuthenticateAsync(" /")); @@ -65,7 +64,7 @@ public async Task TestAuthenticateAsyncWithInvalidId_FailsAsync() Assert.False(await principal.AuthenticateAsync(" /mid")); Assert.False(await principal.AuthenticateAsync("did/mid/blah")); } - + [Fact] public async Task TestAsyncAuthentionReturnsFalse_FailsAsync() { @@ -79,12 +78,14 @@ public async Task TestAsyncAuthentionReturnsFalse_FailsAsync() string deviceId = "myDid"; string moduleId = "myMid"; - Mock.Get(clientCredentialsFactory).Setup(f => f.GetWithX509Cert(deviceId, - moduleId, - string.Empty, - certificate, - chain)) - .Returns(clientCredentials); + Mock.Get(clientCredentialsFactory).Setup( + f => f.GetWithX509Cert( + deviceId, + moduleId, + string.Empty, + certificate, + chain)) + .Returns(clientCredentials); Mock.Get(authenticator).Setup(a => a.AuthenticateAsync(clientCredentials)).ReturnsAsync(false); Assert.False(await principal.AuthenticateAsync($"{deviceId}/{moduleId}")); @@ -103,12 +104,14 @@ public async Task TestAsyncAuthentionDeviceIdReturnsTrus_SucceedsAsync() string deviceId = "myDid"; string moduleId = string.Empty; - Mock.Get(clientCredentialsFactory).Setup(f => f.GetWithX509Cert(deviceId, - moduleId, - string.Empty, - certificate, - chain)) - .Returns(clientCredentials); + Mock.Get(clientCredentialsFactory).Setup( + f => f.GetWithX509Cert( + deviceId, + moduleId, + string.Empty, + certificate, + chain)) + .Returns(clientCredentials); Mock.Get(authenticator).Setup(a => a.AuthenticateAsync(clientCredentials)).ReturnsAsync(true); Assert.True(await principal.AuthenticateAsync($"{deviceId}")); @@ -127,12 +130,14 @@ public async Task TestAsyncAuthentionModuleIdReturnsTrus_SucceedsAsync() string deviceId = "myDid"; string moduleId = "myMid"; - Mock.Get(clientCredentialsFactory).Setup(f => f.GetWithX509Cert(deviceId, - moduleId, - string.Empty, - certificate, - chain)) - .Returns(clientCredentials); + Mock.Get(clientCredentialsFactory).Setup( + f => f.GetWithX509Cert( + deviceId, + moduleId, + string.Empty, + certificate, + chain)) + .Returns(clientCredentials); Mock.Get(authenticator).Setup(a => a.AuthenticateAsync(clientCredentials)).ReturnsAsync(true); Assert.True(await principal.AuthenticateAsync($"{deviceId}/{moduleId}")); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EventsLinkHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EventsLinkHandlerTest.cs index cfa4babaa81..4c68a41201f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EventsLinkHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/EventsLinkHandlerTest.cs @@ -13,7 +13,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; using Xunit; @@ -60,9 +59,10 @@ public async Task SendMessageTest() amqpAuthenticator.Setup(c => c.AuthenticateAsync("d1")).ReturnsAsync(true); Mock cbsNodeMock = amqpAuthenticator.As(); ICbsNode cbsNode = cbsNodeMock.Object; - var amqpConnection = Mock.Of(c => - c.FindExtension() == connectionHandler && - c.FindExtension() == cbsNode); + var amqpConnection = Mock.Of( + c => + c.FindExtension() == connectionHandler && + c.FindExtension() == cbsNode); var amqpSession = Mock.Of(s => s.Connection == amqpConnection); var amqpLink = Mock.Of(l => l.Session == amqpSession && l.IsReceiver && l.Settings == new AmqpLinkSettings() && l.State == AmqpObjectState.Opened); @@ -101,6 +101,7 @@ await WaitAndAssert( { return false; } + IList receivedMessagesList = receivedMessages.ToList(); Assert.Equal(1, receivedMessagesList.Count); Assert.Equal(receivedMessagesList[0].Properties["Prop1"], "Value1"); @@ -132,9 +133,10 @@ public async Task SendMessageBatchTest() amqpAuthenticator.Setup(c => c.AuthenticateAsync("d1")).ReturnsAsync(true); Mock cbsNodeMock = amqpAuthenticator.As(); ICbsNode cbsNode = cbsNodeMock.Object; - var amqpConnection = Mock.Of(c => - c.FindExtension() == connectionHandler && - c.FindExtension() == cbsNode); + var amqpConnection = Mock.Of( + c => + c.FindExtension() == connectionHandler && + c.FindExtension() == cbsNode); var amqpSession = Mock.Of(s => s.Connection == amqpConnection); var amqpLink = Mock.Of(l => l.Session == amqpSession && l.IsReceiver && l.Settings == new AmqpLinkSettings() && l.State == AmqpObjectState.Opened); @@ -181,6 +183,7 @@ await WaitAndAssert( { return false; } + IList receivedMessagesList = receivedMessages.ToList(); Assert.Equal(contents.Count, receivedMessagesList.Count); @@ -194,6 +197,7 @@ await WaitAndAssert( Assert.Equal($"{i}", receivedMessage.Properties["MsgCnt"]); Assert.Equal(contents[i], receivedMessage.Properties["MsgData"]); } + return true; }, TimeSpan.FromSeconds(5)); @@ -206,7 +210,7 @@ public async Task SendLargeMessageThrowsTest() // Arrange bool disposeMessageCalled = true; var identity = Mock.Of(i => i.Id == "d1"); - + var deviceListener = Mock.Of(); Mock.Get(deviceListener).Setup(d => d.ProcessDeviceMessageBatchAsync(It.IsAny>())) .Returns(Task.CompletedTask); @@ -216,9 +220,10 @@ public async Task SendLargeMessageThrowsTest() amqpAuthenticator.Setup(c => c.AuthenticateAsync("d1")).ReturnsAsync(true); Mock cbsNodeMock = amqpAuthenticator.As(); ICbsNode cbsNode = cbsNodeMock.Object; - var amqpConnection = Mock.Of(c => - c.FindExtension() == connectionHandler && - c.FindExtension() == cbsNode); + var amqpConnection = Mock.Of( + c => + c.FindExtension() == connectionHandler && + c.FindExtension() == cbsNode); var amqpSession = Mock.Of(s => s.Connection == amqpConnection); var amqpLink = Mock.Of(l => l.Session == amqpSession && l.IsReceiver && l.Settings == new AmqpLinkSettings() && l.State == AmqpObjectState.Opened); @@ -256,7 +261,7 @@ public async Task SendLargeMessageThrowsTest() public void ExpandBatchMessageTest() { // Arrange - string content1 = "Message1 Contents ABC"; + string content1 = "Message1 Contents ABC"; string content2 = "Message2 Contents PQR"; string content3 = "Message3 Contents XYZ"; var contents = new List @@ -275,7 +280,7 @@ public void ExpandBatchMessageTest() Assert.NotNull(expandedAmqpMessages); Assert.Equal(contents.Count, expandedAmqpMessages.Count); - for(int i = 0; i < expandedAmqpMessages.Count; i++) + for (int i = 0; i < expandedAmqpMessages.Count; i++) { AmqpMessage amqpMessage = expandedAmqpMessages[i]; string actualContents = Encoding.UTF8.GetString(GetMessageBody(amqpMessage)); @@ -302,7 +307,7 @@ static AmqpMessage GetBatchedMessage(IEnumerable contents) var messageList = new List(); int ctr = 0; foreach (string msgContent in contents) - { + { byte[] bytes = Encoding.UTF8.GetBytes(msgContent); using (AmqpMessage msg = AmqpMessage.Create(new MemoryStream(bytes), false)) { @@ -317,6 +322,7 @@ static AmqpMessage GetBatchedMessage(IEnumerable contents) messageList.Add(data); } } + AmqpMessage amqpMessage = AmqpMessage.Create(messageList); amqpMessage.MessageFormat = AmqpConstants.AmqpBatchedMessageFormat; return amqpMessage; @@ -345,6 +351,7 @@ static async Task WaitAndAssert(Func assertBlock, TimeSpan timeout) { Assert.True(false, "Test timed out waiting to complete"); } + await Task.Delay(sleepTime); timespan += sleepTime; } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/LinkHandlerProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/LinkHandlerProviderTest.cs index 4188f493a9e..40b62e3f471 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/LinkHandlerProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/LinkHandlerProviderTest.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Amqp; using Microsoft.Azure.Devices.Edge.Hub.Amqp.LinkHandlers; using Microsoft.Azure.Devices.Edge.Hub.Core; - using Microsoft.Azure.Devices.Edge.Hub.Core.Device; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; @@ -15,66 +14,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test [Unit] public class LinkHandlerProviderTest { - static IEnumerable GetLinkTypeTestData() - { - yield return new object[] { "amqps://foo.bar/$cbs", true, LinkType.Cbs, new Dictionary() }; - yield return new object[] { "amqps://foo.bar/$cbs", false, LinkType.Cbs, new Dictionary() }; - yield return new object[] { "amqps://foo.bar//devices/device1/messages/events", true, LinkType.Events, new Dictionary { { "deviceid", "device1" } } }; - yield return new object[] - { - "amqps://foo.bar/devices/device1/modules/module1/messages/events", true, LinkType.Events, new Dictionary - { - { "deviceid", "device1" }, - { "moduleid", "module1" } - } - }; - yield return new object[] - { - "amqps://foo.bar/devices/device1/modules/module1/messages/events", false, LinkType.ModuleMessages, new Dictionary - { - { "deviceid", "device1" }, - { "moduleid", "module1" } - } - }; - yield return new object[] { "amqps://foo.bar/devices/device1/messages/deviceBound", false, LinkType.C2D, new Dictionary { { "deviceid", "device1" } } }; - yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", false, LinkType.MethodSending, new Dictionary { { "deviceid", "device1" } } }; - yield return new object[] - { - "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", false, LinkType.MethodSending, new Dictionary - { - { "deviceid", "device1" }, - { "moduleid", "module1" } - } - }; - yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", true, LinkType.MethodReceiving, new Dictionary { { "deviceid", "device1" } } }; - yield return new object[] - { - "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", true, LinkType.MethodReceiving, new Dictionary - { - { "deviceid", "device1" }, - { "moduleid", "module1" } - } - }; - yield return new object[] { "amqps://foo.bar/devices/device1/twin", false, LinkType.TwinSending, new Dictionary { { "deviceid", "device1" } } }; - yield return new object[] - { - "amqps://foo.bar/devices/device1/modules/module1/twin", false, LinkType.TwinSending, new Dictionary - { - { "deviceid", "device1" }, - { "moduleid", "module1" } - } - }; - yield return new object[] { "amqps://foo.bar/devices/device1/twin", true, LinkType.TwinReceiving, new Dictionary { { "deviceid", "device1" } } }; - yield return new object[] - { - "amqps://foo.bar/devices/device1/modules/module1/twin", true, LinkType.TwinReceiving, new Dictionary - { - { "deviceid", "device1" }, - { "moduleid", "module1" } - } - }; - } - [Theory] [MemberData(nameof(GetLinkTypeTestData))] public void GetLinkTypeTest(string linkUri, bool isReceiver, LinkType expectedLinkType, IDictionary expectedBoundVariables) @@ -97,18 +36,6 @@ public void GetLinkTypeTest(string linkUri, bool isReceiver, LinkType expectedLi Assert.Equal(expectedBoundVariables, match.BoundVariables); } - static IEnumerable GetInvalidLinkTypeTestData() - { - yield return new object[] { "amqps://foo.bar/$cbs2", true }; - yield return new object[] { "amqps://foo.bar/cbs", false }; - yield return new object[] { "amqps://foo.bar//devices/device1/messages/events", false }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages", true }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages/foo", false }; - yield return new object[] { "amqps://foo.bar/devices/device1/messages/deviceBound", true }; - yield return new object[] { "amqps://foo.bar/devices/device1/twin2", false }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/twin/method", true }; - } - [Theory] [MemberData(nameof(GetInvalidLinkTypeTestData))] public void GetInvalidLinkTypeTest(string linkUri, bool isReceiver) @@ -127,24 +54,6 @@ public void GetInvalidLinkTypeTest(string linkUri, bool isReceiver) Assert.Throws(() => linkHandlerProvider.GetLinkType(amqpLink, uri)); } - static IEnumerable GetLinkHandlerTestData() - { - yield return new object[] { "amqps://foo.bar/$cbs", true, typeof(CbsLinkHandler) }; - yield return new object[] { "amqps://foo.bar/$cbs", false, typeof(CbsLinkHandler) }; - yield return new object[] { "amqps://foo.bar//devices/device1/messages/events", true, typeof(EventsLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages/events", true, typeof(EventsLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages/events", false, typeof(ModuleMessageLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/messages/deviceBound", false, typeof(DeviceBoundLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", false, typeof(MethodSendingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", false, typeof(MethodSendingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", true, typeof(MethodReceivingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", true, typeof(MethodReceivingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/twin", false, typeof(TwinSendingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/twin", false, typeof(TwinSendingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/twin", true, typeof(TwinReceivingLinkHandler) }; - yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/twin", true, typeof(TwinReceivingLinkHandler) }; - } - [Theory] [MemberData(nameof(GetLinkHandlerTestData))] public void GetLinkHandlerTest(string url, bool isReceiver, Type expectedLinkHandlerType) @@ -204,6 +113,96 @@ public void GetIdentityFromBoundVariablesTest(IDictionary boundV } } + static IEnumerable GetLinkTypeTestData() + { + yield return new object[] { "amqps://foo.bar/$cbs", true, LinkType.Cbs, new Dictionary() }; + yield return new object[] { "amqps://foo.bar/$cbs", false, LinkType.Cbs, new Dictionary() }; + yield return new object[] { "amqps://foo.bar//devices/device1/messages/events", true, LinkType.Events, new Dictionary { { "deviceid", "device1" } } }; + yield return new object[] + { + "amqps://foo.bar/devices/device1/modules/module1/messages/events", true, LinkType.Events, new Dictionary + { + { "deviceid", "device1" }, + { "moduleid", "module1" } + } + }; + yield return new object[] + { + "amqps://foo.bar/devices/device1/modules/module1/messages/events", false, LinkType.ModuleMessages, new Dictionary + { + { "deviceid", "device1" }, + { "moduleid", "module1" } + } + }; + yield return new object[] { "amqps://foo.bar/devices/device1/messages/deviceBound", false, LinkType.C2D, new Dictionary { { "deviceid", "device1" } } }; + yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", false, LinkType.MethodSending, new Dictionary { { "deviceid", "device1" } } }; + yield return new object[] + { + "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", false, LinkType.MethodSending, new Dictionary + { + { "deviceid", "device1" }, + { "moduleid", "module1" } + } + }; + yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", true, LinkType.MethodReceiving, new Dictionary { { "deviceid", "device1" } } }; + yield return new object[] + { + "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", true, LinkType.MethodReceiving, new Dictionary + { + { "deviceid", "device1" }, + { "moduleid", "module1" } + } + }; + yield return new object[] { "amqps://foo.bar/devices/device1/twin", false, LinkType.TwinSending, new Dictionary { { "deviceid", "device1" } } }; + yield return new object[] + { + "amqps://foo.bar/devices/device1/modules/module1/twin", false, LinkType.TwinSending, new Dictionary + { + { "deviceid", "device1" }, + { "moduleid", "module1" } + } + }; + yield return new object[] { "amqps://foo.bar/devices/device1/twin", true, LinkType.TwinReceiving, new Dictionary { { "deviceid", "device1" } } }; + yield return new object[] + { + "amqps://foo.bar/devices/device1/modules/module1/twin", true, LinkType.TwinReceiving, new Dictionary + { + { "deviceid", "device1" }, + { "moduleid", "module1" } + } + }; + } + + static IEnumerable GetInvalidLinkTypeTestData() + { + yield return new object[] { "amqps://foo.bar/$cbs2", true }; + yield return new object[] { "amqps://foo.bar/cbs", false }; + yield return new object[] { "amqps://foo.bar//devices/device1/messages/events", false }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages", true }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages/foo", false }; + yield return new object[] { "amqps://foo.bar/devices/device1/messages/deviceBound", true }; + yield return new object[] { "amqps://foo.bar/devices/device1/twin2", false }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/twin/method", true }; + } + + static IEnumerable GetLinkHandlerTestData() + { + yield return new object[] { "amqps://foo.bar/$cbs", true, typeof(CbsLinkHandler) }; + yield return new object[] { "amqps://foo.bar/$cbs", false, typeof(CbsLinkHandler) }; + yield return new object[] { "amqps://foo.bar//devices/device1/messages/events", true, typeof(EventsLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages/events", true, typeof(EventsLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/messages/events", false, typeof(ModuleMessageLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/messages/deviceBound", false, typeof(DeviceBoundLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", false, typeof(MethodSendingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", false, typeof(MethodSendingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/methods/deviceBound", true, typeof(MethodReceivingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/methods/deviceBound", true, typeof(MethodReceivingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/twin", false, typeof(TwinSendingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/twin", false, typeof(TwinSendingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/twin", true, typeof(TwinReceivingLinkHandler) }; + yield return new object[] { "amqps://foo.bar/devices/device1/modules/module1/twin", true, typeof(TwinReceivingLinkHandler) }; + } + static IEnumerable GetIdentityFromBoundVariablesTestData() { yield return new object[] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test.csproj index 19b9ba3ffc5..36206d3546e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test.csproj @@ -35,4 +35,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ReceivingLinkHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ReceivingLinkHandlerTest.cs index 5b80f4f7a5d..db398807de7 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ReceivingLinkHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ReceivingLinkHandlerTest.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Amqp.Constants; [Unit] public class ReceivingLinkHandlerTest @@ -43,7 +44,7 @@ public async Task ReceiveMessageTest() // Act var receivingLinkHandler = new TestReceivingLinkHandler(identity, receivingLink, requestUri, boundVariables, connectionHandler, messageConverter); - await receivingLinkHandler.OpenAsync(Amqp.Constants.DefaultTimeout); + await receivingLinkHandler.OpenAsync(Constants.DefaultTimeout); await receivingLinkHandler.ProcessMessageAsync(message); // Assert @@ -68,10 +69,10 @@ public TestReceivingLinkHandler( public override LinkType Type => LinkType.Events; - protected override QualityOfService QualityOfService => QualityOfService.AtLeastOnce; - public IList ReceivedMessages { get; } = new List(); + protected override QualityOfService QualityOfService => QualityOfService.AtLeastOnce; + protected override Task OnMessageReceived(AmqpMessage amqpMessage) { this.ReceivedMessages.Add(amqpMessage); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SaslIdentityTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SaslIdentityTest.cs index 015b5098c01..56274c39aac 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SaslIdentityTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SaslIdentityTest.cs @@ -3,26 +3,30 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test { using System; using System.Collections.Generic; - using Microsoft.Azure.Devices.Edge.Hub.Amqp; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; public class SaslIdentityTest { - static IEnumerable GetInvalidParseInputs() => new[] - { - new object[] { string.Empty, typeof(ArgumentException) }, - new object[] { " ", typeof(ArgumentException) }, - new object[] { "boo", typeof(EdgeHubConnectionException) }, - }; - [Theory] [Unit] [MemberData(nameof(GetInvalidParseInputs))] public void TestInvalidParseInputs(string input, Type exceptionType) => Assert.Throws(exceptionType, () => SaslIdentity.Parse(input)); + [Theory] + [Unit] + [MemberData(nameof(GetValidParseInputs))] + public void TestValidParseInputs(string input) => Assert.NotNull(SaslIdentity.Parse(input)); + + static IEnumerable GetInvalidParseInputs() => new[] + { + new object[] { string.Empty, typeof(ArgumentException) }, + new object[] { " ", typeof(ArgumentException) }, + new object[] { "boo", typeof(EdgeHubConnectionException) }, + }; + static IEnumerable GetValidParseInputs() => new[] { // DeviceSasIdentityRegex @@ -35,10 +39,5 @@ static IEnumerable GetValidParseInputs() => new[] new object[] { "dev1/modules/mod1@boo" }, new object[] { "dev1@boo" } }; - - [Theory] - [Unit] - [MemberData(nameof(GetValidParseInputs))] - public void TestValidParseInputs(string input) => Assert.NotNull(SaslIdentity.Parse(input)); } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SendingLinkHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SendingLinkHandlerTest.cs index d487dff09ae..88540c92841 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SendingLinkHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/SendingLinkHandlerTest.cs @@ -18,6 +18,13 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test [Unit] public class SendingLinkHandlerTest { + public static IEnumerable FeedbackStatusTestData() + { + yield return new object[] { AmqpConstants.AcceptedOutcome.DescriptorCode, FeedbackStatus.Complete }; + yield return new object[] { AmqpConstants.RejectedOutcome.DescriptorCode, FeedbackStatus.Reject }; + yield return new object[] { AmqpConstants.ReleasedOutcome.DescriptorCode, FeedbackStatus.Abandon }; + } + [Fact] public async Task SendMessageWithFeedbackTest() { @@ -34,9 +41,10 @@ public async Task SendMessageWithFeedbackTest() amqpAuthenticator.Setup(c => c.AuthenticateAsync("d1")).ReturnsAsync(true); Mock cbsNodeMock = amqpAuthenticator.As(); ICbsNode cbsNode = cbsNodeMock.Object; - var amqpConnection = Mock.Of(c => - c.FindExtension() == connectionHandler && - c.FindExtension() == cbsNode); + var amqpConnection = Mock.Of( + c => + c.FindExtension() == connectionHandler && + c.FindExtension() == cbsNode); var amqpSession = Mock.Of(s => s.Connection == amqpConnection); var amqpLinkSettings = new AmqpLinkSettings(); var sendingLink = Mock.Of(l => l.Session == amqpSession && !l.IsReceiver && l.Settings == amqpLinkSettings && l.State == AmqpObjectState.Opened); @@ -50,10 +58,10 @@ public async Task SendMessageWithFeedbackTest() var sendingLinkHandler = new TestSendingLinkHandler(identity, sendingLink, requestUri, boundVariables, connectionHandler, messageConverter, QualityOfService.AtLeastOnce); var body = new byte[] { 0, 1, 2, 3 }; IMessage message = new EdgeMessage.Builder(body).Build(); - var deliveryState = new Mock(new AmqpSymbol(""), AmqpConstants.AcceptedOutcome.DescriptorCode); + var deliveryState = new Mock(new AmqpSymbol(string.Empty), AmqpConstants.AcceptedOutcome.DescriptorCode); var delivery = Mock.Of( d => d.State == deliveryState.Object - && d.DeliveryTag == new ArraySegment(Guid.NewGuid().ToByteArray())); + && d.DeliveryTag == new ArraySegment(Guid.NewGuid().ToByteArray())); // Act await sendingLinkHandler.OpenAsync(TimeSpan.FromSeconds(5)); @@ -89,9 +97,10 @@ public async Task SendMessageWithFeedbackExactlyOnceModeTest() amqpAuthenticator.Setup(c => c.AuthenticateAsync("d1")).ReturnsAsync(true); Mock cbsNodeMock = amqpAuthenticator.As(); ICbsNode cbsNode = cbsNodeMock.Object; - var amqpConnection = Mock.Of(c => - c.FindExtension() == connectionHandler && - c.FindExtension() == cbsNode); + var amqpConnection = Mock.Of( + c => + c.FindExtension() == connectionHandler && + c.FindExtension() == cbsNode); var amqpSession = Mock.Of(s => s.Connection == amqpConnection); var amqpLinkSettings = new AmqpLinkSettings(); var sendingLink = Mock.Of(l => l.Session == amqpSession && !l.IsReceiver && l.Settings == amqpLinkSettings && l.State == AmqpObjectState.Opened); @@ -105,10 +114,10 @@ public async Task SendMessageWithFeedbackExactlyOnceModeTest() var sendingLinkHandler = new TestSendingLinkHandler(identity, sendingLink, requestUri, boundVariables, connectionHandler, messageConverter, QualityOfService.ExactlyOnce); var body = new byte[] { 0, 1, 2, 3 }; IMessage message = new EdgeMessage.Builder(body).Build(); - var deliveryState = new Mock(new AmqpSymbol(""), AmqpConstants.AcceptedOutcome.DescriptorCode); + var deliveryState = new Mock(new AmqpSymbol(string.Empty), AmqpConstants.AcceptedOutcome.DescriptorCode); var delivery = Mock.Of( d => d.State == deliveryState.Object - && d.DeliveryTag == new ArraySegment(Guid.NewGuid().ToByteArray())); + && d.DeliveryTag == new ArraySegment(Guid.NewGuid().ToByteArray())); // Act await sendingLinkHandler.OpenAsync(TimeSpan.FromSeconds(5)); @@ -142,9 +151,10 @@ public async Task SendMessageWithNoFeedbackTest() amqpAuthenticator.Setup(c => c.AuthenticateAsync("d1")).ReturnsAsync(true); Mock cbsNodeMock = amqpAuthenticator.As(); ICbsNode cbsNode = cbsNodeMock.Object; - var amqpConnection = Mock.Of(c => - c.FindExtension() == connectionHandler && - c.FindExtension() == cbsNode); + var amqpConnection = Mock.Of( + c => + c.FindExtension() == connectionHandler && + c.FindExtension() == cbsNode); var amqpSession = Mock.Of(s => s.Connection == amqpConnection); var amqpLinkSettings = new AmqpLinkSettings(); var sendingLink = Mock.Of(l => l.Session == amqpSession && !l.IsReceiver && l.Settings == amqpLinkSettings && l.State == AmqpObjectState.Opened); @@ -176,7 +186,7 @@ public async Task SendMessageWithNoFeedbackTest() public void GetFeedbackStatusTest(ulong descriptorCode, FeedbackStatus expectedFeedbackStatus) { // Arrange - var deliveryState = new Mock(new AmqpSymbol(""), descriptorCode); + var deliveryState = new Mock(new AmqpSymbol(string.Empty), descriptorCode); var delivery = Mock.Of(d => d.State == deliveryState.Object); // Act @@ -185,13 +195,6 @@ public void GetFeedbackStatusTest(ulong descriptorCode, FeedbackStatus expectedF // Assert Assert.Equal(feedbackStatus, expectedFeedbackStatus); } - - public static IEnumerable FeedbackStatusTestData() - { - yield return new object[] { AmqpConstants.AcceptedOutcome.DescriptorCode, FeedbackStatus.Complete }; - yield return new object[] { AmqpConstants.RejectedOutcome.DescriptorCode, FeedbackStatus.Reject }; - yield return new object[] { AmqpConstants.ReleasedOutcome.DescriptorCode, FeedbackStatus.Abandon }; - } } class TestSendingLinkHandler : SendingLinkHandler diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ServerWebSocketTransportTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ServerWebSocketTransportTest.cs index 950ec901632..4439db8f743 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ServerWebSocketTransportTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Amqp.Test/ServerWebSocketTransportTest.cs @@ -11,18 +11,16 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Amqp.Test using Microsoft.Azure.Amqp.Transport; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; - using Moq; using Xunit; + using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; [Unit] public class ServerWebSocketTransportTest { - readonly static X509Certificate2 clientCert = TestCertificateHelper.GenerateSelfSignedCert("test moi"); - readonly static IList clientCertChain = new List() { clientCert }; + static readonly X509Certificate2 clientCert = TestCertificateHelper.GenerateSelfSignedCert("test moi"); + static readonly IList clientCertChain = new List() { clientCert }; readonly IAuthenticator authenticator = Mock.Of(); readonly IClientCredentialsFactory credentialsProvider = Mock.Of(); @@ -42,15 +40,15 @@ public void InvalidCtorWithCertTestsFail() { var webSocket = new Mock(); var guid = Guid.NewGuid().ToString(); - Assert.Throws(() => new ServerWebSocketTransport(null, "local", "remote", guid, clientCert, clientCertChain, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, null, "remote", guid, clientCert, clientCertChain, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", null, guid, clientCert, clientCertChain, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", null, clientCert, clientCertChain, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", " ", clientCert, clientCertChain, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, null, clientCertChain, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, null, authenticator, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, clientCertChain, null, credentialsProvider)); - Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, clientCertChain, authenticator, null)); + Assert.Throws(() => new ServerWebSocketTransport(null, "local", "remote", guid, clientCert, clientCertChain, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, null, "remote", guid, clientCert, clientCertChain, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", null, guid, clientCert, clientCertChain, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", null, clientCert, clientCertChain, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", " ", clientCert, clientCertChain, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, null, clientCertChain, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, null, this.authenticator, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, clientCertChain, null, this.credentialsProvider)); + Assert.Throws(() => new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, clientCertChain, this.authenticator, null)); } [Fact] @@ -58,7 +56,7 @@ public void ValidCtorWithCertTestsSucceeds() { var webSocket = new Mock(); var guid = Guid.NewGuid().ToString(); - var swst = new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, clientCertChain, authenticator, credentialsProvider); + var swst = new ServerWebSocketTransport(webSocket.Object, "local", "remote", guid, clientCert, clientCertChain, this.authenticator, this.credentialsProvider); Assert.NotNull(swst.Principal); } @@ -66,10 +64,11 @@ public void ValidCtorWithCertTestsSucceeds() public void ReadAsyncThrowsWhenBufferIsNull() { var webSocket = new Mock(); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); Assert.Throws(() => serverTransport.ReadAsync(new TransportAsyncCallbackArgs())); } @@ -78,10 +77,11 @@ public void ReadAsyncThrowsWhenBufferIsNull() public void ReadAsyncThrowsWhenCompletedCallbackIsNull() { var webSocket = new Mock(); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], 0, 4); @@ -98,14 +98,15 @@ public void ReadAsyncThrowsWhenSocketNotOpen() .Returns(WebSocketState.CloseSent) .Returns(WebSocketState.CloseReceived) .Returns(WebSocketState.None); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], 0, 4); - args.CompletedCallback += delegate (TransportAsyncCallbackArgs callbackArgs) { }; + args.CompletedCallback += callbackArgs => { }; Assert.Throws(() => serverTransport.ReadAsync(args)); Assert.Throws(() => serverTransport.ReadAsync(args)); Assert.Throws(() => serverTransport.ReadAsync(args)); @@ -120,14 +121,15 @@ public void ReadAsyncThrowsWhenBufferLimits() webSocket.Setup(ws => ws.State) .Returns(WebSocketState.Open); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], -1, 4); - args.CompletedCallback += delegate { }; + args.CompletedCallback += obj => { }; Assert.Throws(() => serverTransport.ReadAsync(args)); args.SetBuffer(new byte[4], 5, 9); @@ -149,14 +151,15 @@ public void ReadAsyncSuccess() webSocket.Setup(ws => ws.ReceiveAsync(It.IsAny>(), It.IsAny())) .Returns(Task.FromResult(new WebSocketReceiveResult(2, WebSocketMessageType.Text, true))); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], 0, 4); - args.CompletedCallback += delegate { }; + args.CompletedCallback += obj => { }; Assert.False(serverTransport.ReadAsync(args)); webSocket.VerifyAll(); @@ -166,10 +169,11 @@ public void ReadAsyncSuccess() public void WriteAsyncThrowsWhenBufferIsNull() { var webSocket = new Mock(); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); Assert.Throws(() => serverTransport.WriteAsync(new TransportAsyncCallbackArgs())); } @@ -178,10 +182,11 @@ public void WriteAsyncThrowsWhenBufferIsNull() public void WriteAsyncThrowsWhenCompletedCallbackIsNull() { var webSocket = new Mock(); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], 0, 4); @@ -198,14 +203,15 @@ public void WriteAsyncThrowsWhenSocketNotOpen() .Returns(WebSocketState.CloseSent) .Returns(WebSocketState.CloseReceived) .Returns(WebSocketState.None); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], 0, 4); - args.CompletedCallback += delegate { }; + args.CompletedCallback += obj => { }; Assert.Throws(() => serverTransport.ReadAsync(args)); Assert.Throws(() => serverTransport.ReadAsync(args)); Assert.Throws(() => serverTransport.ReadAsync(args)); @@ -222,14 +228,15 @@ public void WriteBufferAsyncSuccess() webSocket.Setup(ws => ws.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new WebSocketReceiveResult(2, WebSocketMessageType.Text, true))); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); args.SetBuffer(new byte[4], 0, 4); - args.CompletedCallback += delegate { }; + args.CompletedCallback += obj => { }; Assert.False(serverTransport.WriteAsync(args)); webSocket.VerifyAll(); @@ -244,14 +251,15 @@ public void WriteBufferListAsyncSuccess() webSocket.Setup(ws => ws.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(new WebSocketReceiveResult(2, WebSocketMessageType.Text, true))); - var serverTransport = new ServerWebSocketTransport(webSocket.Object, - "local", - "remote", - Guid.NewGuid().ToString()); + var serverTransport = new ServerWebSocketTransport( + webSocket.Object, + "local", + "remote", + Guid.NewGuid().ToString()); var args = new TransportAsyncCallbackArgs(); - args.SetBuffer(new List { new ByteBuffer(new byte[4]), new ByteBuffer(new byte[5])}); - args.CompletedCallback += delegate { }; + args.SetBuffer(new List { new ByteBuffer(new byte[4]), new ByteBuffer(new byte[5]) }); + args.CompletedCallback += obj => { }; Assert.False(serverTransport.WriteAsync(args)); webSocket.Verify(ws => ws.SendAsync(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ClientTokenCloudConnectionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ClientTokenCloudConnectionTest.cs index 40a17a0cfc3..732a3434f20 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ClientTokenCloudConnectionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ClientTokenCloudConnectionTest.cs @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test using Microsoft.Azure.Devices.Shared; using Moq; using Xunit; - using TransportType = Microsoft.Azure.Devices.Client.TransportType; [Unit] public class ClientTokenCloudConnectionTest @@ -150,7 +149,7 @@ ITokenCredentials GetClientCredentialsWithNonExpiringToken() ITokenProvider tokenProvider = null; IClientProvider clientProvider = GetMockDeviceClientProviderWithToken((s, a, t) => tokenProvider = a); - var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(Client.TransportType.Amqp_Tcp_Only) }; + var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) }; var receivedStatus = CloudConnectionStatus.ConnectionEstablished; void ConnectionStatusHandler(string id, CloudConnectionStatus status) => receivedStatus = status; @@ -217,7 +216,7 @@ ITokenCredentials GetClientCredentialsWithNonExpiringToken() ITokenProvider tokenProvider = null; IClientProvider clientProvider = GetMockDeviceClientProviderWithToken((s, a, t) => tokenProvider = a); - var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(Client.TransportType.Amqp_Tcp_Only) }; + var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) }; var receivedStatuses = new List(); void ConnectionStatusHandler(string id, CloudConnectionStatus status) => receivedStatuses.Add(status); @@ -313,7 +312,7 @@ IClient GetMockedDeviceClient() deviceClientProvider.Setup(dc => dc.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(() => GetMockedDeviceClient()); - var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(Client.TransportType.Amqp_Tcp_Only) }; + var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) }; void ConnectionStatusHandler(string id, CloudConnectionStatus status) { diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionProviderTest.cs index b417b46f7d7..aa6ebb18be2 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionProviderTest.cs @@ -19,14 +19,90 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test [Unit] public class CloudConnectionProviderTest { + const string IotHubHostName = "foo.azure-devices.net"; + const int ConnectionPoolSize = 10; static readonly IMessageConverterProvider MessageConverterProvider = Mock.Of(); static readonly ITokenProvider TokenProvider = Mock.Of(); static readonly IDeviceScopeIdentitiesCache DeviceScopeIdentitiesCache = Mock.Of(); static readonly ICredentialsCache CredentialsCache = Mock.Of(); static readonly IIdentity EdgeHubIdentity = Mock.Of(i => i.Id == "device1/$edgeHub"); - const string IotHubHostName = "foo.azure-devices.net"; - const int ConnectionPoolSize = 10; + public static IEnumerable UpstreamProtocolTransportSettingsData() + { + yield return new object[] + { + Option.None(), + 20, + new ITransportSettings[] + { + new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) + { + AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings + { + Pooling = true, + MaxPoolSize = 20, + ConnectionIdleTimeout = TimeSpan.FromSeconds(5) + } + } + } + }; + + yield return new object[] + { + Option.Some(UpstreamProtocol.Amqp), + 30, + new ITransportSettings[] + { + new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) + { + AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings + { + Pooling = true, + MaxPoolSize = 30, + ConnectionIdleTimeout = TimeSpan.FromSeconds(5) + } + } + } + }; + + yield return new object[] + { + Option.Some(UpstreamProtocol.AmqpWs), + 50, + new ITransportSettings[] + { + new AmqpTransportSettings(TransportType.Amqp_WebSocket_Only) + { + AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings + { + Pooling = true, + MaxPoolSize = 50, + ConnectionIdleTimeout = TimeSpan.FromSeconds(5) + } + } + } + }; + + yield return new object[] + { + Option.Some(UpstreamProtocol.Mqtt), + 60, + new ITransportSettings[] + { + new MqttTransportSettings(TransportType.Mqtt_Tcp_Only) + } + }; + + yield return new object[] + { + Option.Some(UpstreamProtocol.MqttWs), + 80, + new ITransportSettings[] + { + new MqttTransportSettings(TransportType.Mqtt_WebSocket_Only) + } + }; + } [Fact] public async Task ConnectUsingTokenCredentialsTest() @@ -102,7 +178,7 @@ public async Task ConnectUsingInvalidTokenCredentialsTest() [Fact] public async Task ConnectUsingIdentityInScopeTest() { - // Arrange + // Arrange var deviceIdentity = Mock.Of(m => m.Id == "d1"); var deviceScopeIdentitiesCache = new Mock(MockBehavior.Strict); @@ -137,7 +213,7 @@ public async Task ConnectUsingIdentityInScopeTest() [Fact] public async Task ConnectUsingIdentityInCacheTest() { - // Arrange + // Arrange var deviceIdentity = Mock.Of(m => m.Id == "d1"); string token = TokenHelper.CreateSasToken(IotHubHostName, DateTime.UtcNow.AddMinutes(10)); var tokenCreds = new TokenCredentials(deviceIdentity, token, string.Empty, false); @@ -178,7 +254,7 @@ public async Task ConnectUsingIdentityInCacheTest() [Fact] public async Task ConnectUsingIdentityInCacheTest2() { - // Arrange + // Arrange var deviceIdentity = Mock.Of(m => m.Id == "d1"); string token = TokenHelper.CreateSasToken(IotHubHostName, DateTime.UtcNow.AddMinutes(10)); var tokenCreds = new TokenCredentials(deviceIdentity, token, string.Empty, false); @@ -237,83 +313,6 @@ public void GetTransportSettingsTest(Option upstreamProtocol, } } - public static IEnumerable UpstreamProtocolTransportSettingsData() - { - yield return new object[] - { - Option.None(), - 20, - new ITransportSettings[] - { - new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) - { - AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings - { - Pooling = true, - MaxPoolSize = 20, - ConnectionIdleTimeout = TimeSpan.FromSeconds(5) - } - } - } - }; - - yield return new object[] - { - Option.Some(UpstreamProtocol.Amqp), - 30, - new ITransportSettings[] - { - new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) - { - AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings - { - Pooling = true, - MaxPoolSize = 30, - ConnectionIdleTimeout = TimeSpan.FromSeconds(5) - } - } - } - }; - - yield return new object[] - { - Option.Some(UpstreamProtocol.AmqpWs), - 50, - new ITransportSettings[] - { - new AmqpTransportSettings(TransportType.Amqp_WebSocket_Only) - { - AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings - { - Pooling = true, - MaxPoolSize = 50, - ConnectionIdleTimeout = TimeSpan.FromSeconds(5) - } - } - } - }; - - yield return new object[] - { - Option.Some(UpstreamProtocol.Mqtt), - 60, - new ITransportSettings[] - { - new MqttTransportSettings(TransportType.Mqtt_Tcp_Only) - } - }; - - yield return new object[] - { - Option.Some(UpstreamProtocol.MqttWs), - 80, - new ITransportSettings[] - { - new MqttTransportSettings(TransportType.Mqtt_WebSocket_Only) - } - }; - } - static IClientProvider GetMockDeviceClientProvider() { var deviceClientProvider = new Mock(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionTest.cs index f29635d50f3..5576f71a33d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudConnectionTest.cs @@ -13,9 +13,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test using Microsoft.Azure.Devices.Shared; using Moq; using Xunit; + [Unit] public class CloudConnectionTest - { + { [Fact] public async Task GetCloudConnectionForIdentityWithKeyTest() { @@ -59,17 +60,18 @@ public async Task GetCloudConnectionThrowsTest() var identity = Mock.Of(i => i.Id == "d1"); var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) }; var messageConverterProvider = new MessageConverterProvider(new Dictionary { [typeof(TwinCollection)] = Mock.Of() }); - await Assert.ThrowsAsync(() => CloudConnection.Create( - identity, - (_, __) => { }, - transportSettings, - messageConverterProvider, - deviceClientProvider.Object, - Mock.Of(), - tokenProvider, - TimeSpan.FromMinutes(60), - true, - TimeSpan.FromSeconds(20))); + await Assert.ThrowsAsync( + () => CloudConnection.Create( + identity, + (_, __) => { }, + transportSettings, + messageConverterProvider, + deviceClientProvider.Object, + Mock.Of(), + tokenProvider, + TimeSpan.FromMinutes(60), + true, + TimeSpan.FromSeconds(20))); } static IClientProvider GetMockDeviceClientProviderWithKey() diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyTest.cs index e64ec30ff96..eee0fadfaed 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyTest.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test using System.Collections.Generic; using System.Text; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Client; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; @@ -142,7 +143,7 @@ public async Task CanListenForDesiredPropertyUpdates() await cloudProxy.RemoveDesiredPropertyUpdatesAsync(); IMessage expected = new EdgeMessage.Builder(Encoding.UTF8.GetBytes(desired.ToJson())).Build(); - expected.SystemProperties[SystemProperties.EnqueuedTime] = ""; + expected.SystemProperties[SystemProperties.EnqueuedTime] = string.Empty; expected.SystemProperties[SystemProperties.Version] = desired.Version.ToString(); IMessage actual = update.Task.Result; @@ -193,14 +194,14 @@ public async Task TestCloseThrows() public async Task TestHandlNre() { // Arrange - var messageConverter = Mock.Of>(m => m.FromMessage(It.IsAny()) == new Client.Message()); - var messageConverterProvider = Mock.Of(m => m.Get() == messageConverter); + var messageConverter = Mock.Of>(m => m.FromMessage(It.IsAny()) == new Message()); + var messageConverterProvider = Mock.Of(m => m.Get() == messageConverter); string clientId = "d1"; var cloudListener = Mock.Of(); TimeSpan idleTimeout = TimeSpan.FromSeconds(60); Action connectionStatusChangedHandler = (s, status) => { }; var client = new Mock(MockBehavior.Strict); - client.Setup(c => c.SendEventAsync(It.IsAny())).ThrowsAsync(new NullReferenceException()); + client.Setup(c => c.SendEventAsync(It.IsAny())).ThrowsAsync(new NullReferenceException()); client.Setup(c => c.CloseAsync()).Returns(Task.CompletedTask); var cloudProxy = new CloudProxy(client.Object, messageConverterProvider, clientId, connectionStatusChangedHandler, cloudListener, idleTimeout, false); IMessage message = new EdgeMessage.Builder(new byte[0]).Build(); @@ -212,6 +213,41 @@ public async Task TestHandlNre() client.VerifyAll(); } + static async Task CheckMessageInEventHub(IList sentMessages, DateTime startTime) + { + string eventHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("eventHubConnStrKey"); + var eventHubReceiver = new EventHubReceiver(eventHubConnectionString); + var cloudMessages = new List(); + bool messagesFound = false; + // Add retry mechanism to make sure all the messages sent reached Event Hub. Retry 3 times. + for (int i = 0; i < EventHubMessageReceivedRetry; i++) + { + cloudMessages.AddRange(await eventHubReceiver.GetMessagesFromAllPartitions(startTime)); + messagesFound = MessageHelper.CompareMessagesAndEventData(sentMessages, cloudMessages); + if (messagesFound) + { + break; + } + + await Task.Delay(TimeSpan.FromSeconds(20)); + } + + await eventHubReceiver.Close(); + Assert.NotNull(cloudMessages); + Assert.NotEmpty(cloudMessages); + Assert.True(messagesFound); + } + + static async Task UpdateDesiredProperty(string deviceId, TwinCollection desired) + { + string connectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); + RegistryManager registryManager = RegistryManager.CreateFromConnectionString(connectionString); + Twin twin = await registryManager.GetTwinAsync(deviceId); + twin.Properties.Desired = desired; + twin = await registryManager.UpdateTwinAsync(deviceId, twin, twin.ETag); + desired["$version"] = twin.Properties.Desired.Version; + } + Task GetCloudProxyWithConnectionStringKey(string connectionStringConfigKey) => this.GetCloudProxyWithConnectionStringKey(connectionStringConfigKey, Mock.Of()); @@ -225,7 +261,7 @@ async Task GetCloudProxyWithConnectionStringKey(string connectionSt var converters = new MessageConverterProvider( new Dictionary() { - { typeof(Client.Message), new DeviceClientMessageConverter() }, + { typeof(Message), new DeviceClientMessageConverter() }, { typeof(Twin), new TwinMessageConverter() }, { typeof(TwinCollection), new TwinCollectionMessageConverter() } }); @@ -244,7 +280,7 @@ async Task GetCloudProxyWithConnectionStringKey(string connectionSt true, TimeSpan.FromSeconds(20)); cloudConnectionProvider.BindEdgeHub(edgeHub); - + var clientTokenProvider = new ClientTokenProvider(new SharedAccessKeySignatureProvider(sasKey), iotHubHostName, deviceId, TimeSpan.FromHours(1)); string token = await clientTokenProvider.GetTokenAsync(Option.None()); var deviceIdentity = new DeviceIdentity(iotHubHostName, deviceId); @@ -256,40 +292,5 @@ async Task GetCloudProxyWithConnectionStringKey(string connectionSt Assert.True(cloudConnection.Value.CloudProxy.HasValue); return cloudConnection.Value.CloudProxy.OrDefault(); } - - static async Task CheckMessageInEventHub(IList sentMessages, DateTime startTime) - { - string eventHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("eventHubConnStrKey"); - var eventHubReceiver = new EventHubReceiver(eventHubConnectionString); - var cloudMessages = new List(); - bool messagesFound = false; - //Add retry mechanism to make sure all the messages sent reached Event Hub. Retry 3 times. - for (int i = 0; i < EventHubMessageReceivedRetry; i++) - { - cloudMessages.AddRange(await eventHubReceiver.GetMessagesFromAllPartitions(startTime)); - messagesFound = MessageHelper.CompareMessagesAndEventData(sentMessages, cloudMessages); - if (messagesFound) - { - break; - } - - await Task.Delay(TimeSpan.FromSeconds(20)); - } - - await eventHubReceiver.Close(); - Assert.NotNull(cloudMessages); - Assert.NotEmpty(cloudMessages); - Assert.True(messagesFound); - } - - static async Task UpdateDesiredProperty(string deviceId, TwinCollection desired) - { - string connectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); - RegistryManager registryManager = RegistryManager.CreateFromConnectionString(connectionString); - Twin twin = await registryManager.GetTwinAsync(deviceId); - twin.Properties.Desired = desired; - twin = await registryManager.UpdateTwinAsync(deviceId, twin, twin.ETag); - desired["$version"] = twin.Properties.Desired.Version; - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyUnitTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyUnitTest.cs index db0883c6c77..f2c6eb8b8ac 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyUnitTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudProxyUnitTest.cs @@ -23,14 +23,14 @@ public async Task TestCloseOnInactive() .Callback(() => isClientActive = false) .Returns(Task.CompletedTask); client.SetupGet(c => c.IsActive).Returns(() => isClientActive); - client.Setup(c => c.SendEventAsync(It.IsAny())).Returns(Task.CompletedTask); + client.Setup(c => c.SendEventAsync(It.IsAny())).Returns(Task.CompletedTask); - var messageConverter = new Mock>(); + var messageConverter = new Mock>(); messageConverter.Setup(m => m.FromMessage(It.IsAny())) - .Returns(new Client.Message()); + .Returns(new Message()); var messageConverterProvider = new Mock(); - messageConverterProvider.Setup(m => m.Get()) + messageConverterProvider.Setup(m => m.Get()) .Returns(messageConverter.Object); var cloudListener = new Mock(); @@ -74,20 +74,20 @@ public async Task TestCloseOnInactiveDisabled() .Callback(() => isClientActive = false) .Returns(Task.CompletedTask); client.SetupGet(c => c.IsActive).Returns(() => isClientActive); - client.Setup(c => c.SendEventAsync(It.IsAny())).Returns(Task.CompletedTask); + client.Setup(c => c.SendEventAsync(It.IsAny())).Returns(Task.CompletedTask); - var messageConverter = new Mock>(); + var messageConverter = new Mock>(); messageConverter.Setup(m => m.FromMessage(It.IsAny())) - .Returns(new Client.Message()); + .Returns(new Message()); var messageConverterProvider = new Mock(); - messageConverterProvider.Setup(m => m.Get()) + messageConverterProvider.Setup(m => m.Get()) .Returns(messageConverter.Object); var cloudListener = new Mock(); TimeSpan idleTimeout = TimeSpan.FromSeconds(5); ICloudProxy cloudProxy = new CloudProxy(client.Object, messageConverterProvider.Object, "device1", null, cloudListener.Object, idleTimeout, false); - + // Act await Task.Delay(TimeSpan.FromSeconds(6)); @@ -148,15 +148,15 @@ public async Task TestDisableOnMethodsSubscription() .Callback(() => isClientActive = false) .Returns(Task.CompletedTask); client.SetupGet(c => c.IsActive).Returns(() => isClientActive); - client.Setup(c => c.SendEventAsync(It.IsAny())).Returns(Task.CompletedTask); + client.Setup(c => c.SendEventAsync(It.IsAny())).Returns(Task.CompletedTask); client.Setup(c => c.SetMethodDefaultHandlerAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - var messageConverter = new Mock>(); + var messageConverter = new Mock>(); messageConverter.Setup(m => m.FromMessage(It.IsAny())) - .Returns(new Client.Message()); + .Returns(new Message()); var messageConverterProvider = new Mock(); - messageConverterProvider.Setup(m => m.Get()) + messageConverterProvider.Setup(m => m.Get()) .Returns(messageConverter.Object); var cloudListener = new Mock(); @@ -190,15 +190,15 @@ public async Task TestDisableTimerOnC2DSubscription() .Returns(Task.CompletedTask); client.SetupGet(c => c.IsActive).Returns(() => isClientActive); client.Setup(c => c.ReceiveAsync(It.IsAny())) - //.Callback(t => Task.Yield()) + // .Callback(t => Task.Yield()) .Returns(Task.FromResult(new Message())); - var messageConverter = new Mock>(); + var messageConverter = new Mock>(); messageConverter.Setup(m => m.FromMessage(It.IsAny())) - .Returns(new Client.Message()); + .Returns(new Message()); var messageConverterProvider = new Mock(); - messageConverterProvider.Setup(m => m.Get()) + messageConverterProvider.Setup(m => m.Get()) .Returns(messageConverter.Object); var cloudListener = new Mock(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudReceiverTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudReceiverTest.cs index 7657a4238bf..2f3a42b0d45 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudReceiverTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/CloudReceiverTest.cs @@ -24,7 +24,7 @@ public class CloudReceiverTest public async Task MethodCallHandler_WhenResponse_WithRequestIdReceived_Completes() { var cloudListener = new Mock(); - cloudListener.Setup(p => p.CallMethodAsync(It.IsAny())).Returns(Task.FromResult(new DirectMethodResponse(RequestId, Data, StatusCode))); + cloudListener.Setup(p => p.CallMethodAsync(It.IsAny())).Returns(Task.FromResult(new DirectMethodResponse(RequestId, Data, StatusCode))); var messageConverter = new Mock(); var identity = Mock.Of(i => i.Id == "device1"); @@ -34,7 +34,7 @@ public async Task MethodCallHandler_WhenResponse_WithRequestIdReceived_Completes CloudProxy.CloudReceiver cloudReceiver = cloudProxy.GetCloudReceiver(); MethodResponse methodResponse = await cloudReceiver.MethodCallHandler(new MethodRequest(MethodName, Data), null); - cloudListener.Verify(p => p.CallMethodAsync(It.Is(x => x.Name == MethodName && x.Data == Data)), Times.Once); + cloudListener.Verify(p => p.CallMethodAsync(It.Is(x => x.Name == MethodName && x.Data == Data)), Times.Once); Assert.NotNull(methodResponse); } @@ -42,11 +42,11 @@ public async Task MethodCallHandler_WhenResponse_WithRequestIdReceived_Completes [Unit] public void ConstructorWithNullCloudListenerThrow() { - //Arrange + // Arrange var messageConverter = new Mock(); var deviceClient = Mock.Of(); - //Act - //Assert + // Act + // Assert Assert.Throws(() => new CloudProxy(deviceClient, messageConverter.Object, "device1", (id, s) => { }, null, TimeSpan.FromMinutes(60), true)); } @@ -54,17 +54,17 @@ public void ConstructorWithNullCloudListenerThrow() [Unit] public void ConstructorHappyPath() { - //Arrange + // Arrange var messageConverter = new Mock(); var identity = Mock.Of(i => i.Id == "device1"); var deviceClient = Mock.Of(); var cloudListener = new Mock(); var cloudProxy = new CloudProxy(deviceClient, messageConverter.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); - //Act + // Act CloudProxy.CloudReceiver cloudReceiver = cloudProxy.GetCloudReceiver(); - //Assert + // Assert Assert.NotNull(cloudReceiver); } @@ -72,18 +72,18 @@ public void ConstructorHappyPath() [Unit] public void CloudReceiverCanProcessAMessage() { - //Arrange + // Arrange var sampleMessage = Mock.Of(); - var messageConverter = new Mock>(); - messageConverter.Setup(p => p.ToMessage(It.IsAny())).Returns(sampleMessage); + var messageConverter = new Mock>(); + messageConverter.Setup(p => p.ToMessage(It.IsAny())).Returns(sampleMessage); var messageConverterProvider = new Mock(); - messageConverterProvider.Setup(p => p.Get()).Returns(messageConverter.Object); + messageConverterProvider.Setup(p => p.Get()).Returns(messageConverter.Object); var identity = Mock.Of(i => i.Id == "device1"); var deviceClient = new Mock(); - deviceClient.Setup(p => p.ReceiveAsync(It.IsAny())).ReturnsAsync(new Client.Message(new byte[0])); + deviceClient.Setup(p => p.ReceiveAsync(It.IsAny())).ReturnsAsync(new Message(new byte[0])); var cloudListener = new Mock(); var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); @@ -94,10 +94,10 @@ public void CloudReceiverCanProcessAMessage() .Returns(Task.CompletedTask) .Callback(() => cloudReceiver.CloseAsync()); - //Act + // Act cloudReceiver.StartListening(); - //Assert + // Assert cloudListener.Verify(m => m.ProcessMessageAsync(sampleMessage)); } @@ -105,42 +105,42 @@ public void CloudReceiverCanProcessAMessage() [Unit] public void CloudReceiverDisconnectsWhenItReceivesUnauthorizedException() { - //Arrange + // Arrange var messageConverterProvider = Mock.Of(); var deviceClient = new Mock(); deviceClient.Setup(p => p.ReceiveAsync(It.IsAny())) - .Throws(new UnauthorizedException("")); + .Throws(new UnauthorizedException(string.Empty)); var cloudListener = Mock.Of(); var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider, "device1", (id, s) => { }, cloudListener, TimeSpan.FromMinutes(60), true); CloudProxy.CloudReceiver cloudReceiver = cloudProxy.GetCloudReceiver(); - //Act - cloudReceiver.StartListening(); //Exception expected to be handled and not thrown. + // Act + cloudReceiver.StartListening(); // Exception expected to be handled and not thrown. - //Assert - //The verification is implicit, just making sure the exception is handled. + // Assert + // The verification is implicit, just making sure the exception is handled. } [Fact] [Unit] public void CloudReceiverIgnoresOtherExceptionsWhenReceivingMessages() { - //Arrange - var messageConverter = new Mock>(); - messageConverter.Setup(p => p.ToMessage(It.IsAny())).Returns(null); + // Arrange + var messageConverter = new Mock>(); + messageConverter.Setup(p => p.ToMessage(It.IsAny())).Returns(null); var messageConverterProvider = new Mock(); - messageConverterProvider.Setup(p => p.Get()).Returns(messageConverter.Object); + messageConverterProvider.Setup(p => p.Get()).Returns(messageConverter.Object); var identity = Mock.Of(i => i.Id == "device1"); var deviceClient = new Mock(); - deviceClient.Setup(p => p.ReceiveAsync(It.IsAny())).ReturnsAsync(new Client.Message(new byte[0])); + deviceClient.Setup(p => p.ReceiveAsync(It.IsAny())).ReturnsAsync(new Message(new byte[0])); var cloudListener = new Mock(); - var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); + var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); CloudProxy.CloudReceiver cloudReceiver = cloudProxy.GetCloudReceiver(); @@ -148,33 +148,32 @@ public void CloudReceiverIgnoresOtherExceptionsWhenReceivingMessages() .Callback(() => cloudReceiver.CloseAsync()) .Throws(new Exception()); - //Act - cloudReceiver.StartListening(); //Exception expected to be handled and not thrown. + // Act + cloudReceiver.StartListening(); // Exception expected to be handled and not thrown. - //Assert + // Assert cloudListener.Verify(m => m.ProcessMessageAsync(null)); } - [Fact] [Unit] public void SetupDesiredPropertyUpdatesAsync() { - //Arrange + // Arrange var messageConverterProvider = new Mock(); var identity = Mock.Of(i => i.Id == "device1"); var deviceClient = new Mock(); var cloudListener = new Mock(); - var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); + var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); CloudProxy.CloudReceiver cloudReceiver = cloudProxy.GetCloudReceiver(); - //Act + // Act cloudReceiver.SetupDesiredPropertyUpdatesAsync(); - //Assert + // Assert deviceClient.Verify(m => m.SetDesiredPropertyUpdateCallbackAsync(It.IsAny(), It.IsAny())); } @@ -182,23 +181,22 @@ public void SetupDesiredPropertyUpdatesAsync() [Unit] public void RemoveDesiredPropertyUpdatesAsync() { - //Arrange + // Arrange var messageConverterProvider = new Mock(); var identity = Mock.Of(i => i.Id == "device1"); var deviceClient = new Mock(); - deviceClient.Setup(dc => dc.SetDesiredPropertyUpdateCallbackAsync(null, null)).Throws(new Exception("Update this test!")); //This is to catch onde the TODO on the code get's in. + deviceClient.Setup(dc => dc.SetDesiredPropertyUpdateCallbackAsync(null, null)).Throws(new Exception("Update this test!")); // This is to catch onde the TODO on the code get's in. var cloudListener = new Mock(); - var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); + var cloudProxy = new CloudProxy(deviceClient.Object, messageConverterProvider.Object, identity.Id, (id, s) => { }, cloudListener.Object, TimeSpan.FromMinutes(60), true); CloudProxy.CloudReceiver cloudReceiver = cloudProxy.GetCloudReceiver(); - //Act + // Act cloudReceiver.RemoveDesiredPropertyUpdatesAsync(); - //Assert - + // Assert } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ConnectivityAwareClientTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ConnectivityAwareClientTest.cs index d7f7ac15c7e..ca498c2bc37 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ConnectivityAwareClientTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ConnectivityAwareClientTest.cs @@ -22,6 +22,7 @@ public async Task DisableHandlingEventsOnCloseTest() { // Arrange int connectionStatusChangedHandlerCount = 0; + void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusChangeReason reason) { Interlocked.Increment(ref connectionStatusChangedHandlerCount); @@ -57,11 +58,11 @@ public async Task TestExceptionTest(Type thrownException, Type expectedException { // Arrange var client = new Mock(); - client.Setup(c => c.SendEventAsync(It.IsAny())).ThrowsAsync(Activator.CreateInstance(thrownException, "msg str") as Exception); + client.Setup(c => c.SendEventAsync(It.IsAny())).ThrowsAsync(Activator.CreateInstance(thrownException, "msg str") as Exception); var deviceConnectivityManager = new Mock(); deviceConnectivityManager.Setup(d => d.CallTimedOut()); var connectivityAwareClient = new ConnectivityAwareClient(client.Object, deviceConnectivityManager.Object, Mock.Of(i => i.Id == "d1")); - var message = new Client.Message(); + var message = new Message(); // Act / Assert await Assert.ThrowsAsync(expectedException, () => connectivityAwareClient.SendEventAsync(message)); @@ -101,6 +102,7 @@ public async Task ConnectivityChangeTest() // Arrange var receivedConnectionStatuses = new List(); var receivedChangeReasons = new List(); + void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusChangeReason reason) { receivedConnectionStatuses.Add(status); @@ -109,14 +111,14 @@ void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusCha var deviceConnectivityManager = new DeviceConnectivityManager(); var client = Mock.Of(); - Mock.Get(client).SetupSequence(c => c.SendEventAsync(It.IsAny())) + Mock.Get(client).SetupSequence(c => c.SendEventAsync(It.IsAny())) .Returns(Task.CompletedTask) .Throws(new TimeoutException()); var connectivityAwareClient = new ConnectivityAwareClient(client, deviceConnectivityManager, Mock.Of(i => i.Id == "d1")); connectivityAwareClient.SetConnectionStatusChangedHandler(ConnectionStatusChangedHandler); // Act - await connectivityAwareClient.SendEventAsync(new Client.Message()); + await connectivityAwareClient.SendEventAsync(new Message()); // Assert Assert.Equal(1, receivedConnectionStatuses.Count); @@ -124,7 +126,7 @@ void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusCha Assert.Equal(ConnectionStatusChangeReason.Connection_Ok, receivedChangeReasons[0]); // Act - await Assert.ThrowsAsync(async () => await connectivityAwareClient.SendEventAsync(new Client.Message())); + await Assert.ThrowsAsync(async () => await connectivityAwareClient.SendEventAsync(new Message())); // Assert Assert.Equal(1, receivedConnectionStatuses.Count); @@ -168,6 +170,10 @@ void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusCha class DeviceConnectivityManager : IDeviceConnectivityManager { + public event EventHandler DeviceConnected; + + public event EventHandler DeviceDisconnected; + public void CallSucceeded() { } @@ -179,10 +185,6 @@ public void CallTimedOut() public void InvokeDeviceConnected() => this.DeviceConnected?.Invoke(null, null); public void InvokeDeviceDisconnected() => this.DeviceDisconnected?.Invoke(null, null); - - public event EventHandler DeviceConnected; - - public event EventHandler DeviceDisconnected; } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceClientMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceClientMessageConverterTest.cs index c9eec36c902..d4e7761516d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceClientMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceClientMessageConverterTest.cs @@ -64,7 +64,8 @@ public void TestErrorCases(IMessage inputMessage, Type exceptionType) [Theory] [Unit] [MemberData(nameof(GetValidMessagesData))] - public void TestValidCases(byte[] messageBytes, + public void TestValidCases( + byte[] messageBytes, IDictionary properties, IDictionary systemProperties) { @@ -100,7 +101,8 @@ public void TestValidCases(byte[] messageBytes, [Theory] [Unit] [MemberData(nameof(GetValidMessagesData))] - public void TestValidCasesToMessage(byte[] messageBytes, + public void TestValidCasesToMessage( + byte[] messageBytes, IDictionary properties, IDictionary systemProperties) { diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceConnectivityManagerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceConnectivityManagerTest.cs index 9a9844a1453..5a10ad3c183 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceConnectivityManagerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceConnectivityManagerTest.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test { - using Microsoft.Azure.Devices.Client; - using Microsoft.Azure.Devices.Edge.Hub.Core; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Shared; - using Moq; using System; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Client; + 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 Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Shared; + using Moq; using Xunit; [Unit] @@ -168,6 +168,7 @@ void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusCha catch (TimeoutException) { } + if (!cts2.IsCancellationRequested) { await Task.Delay(TimeSpan.FromSeconds(1)); @@ -193,7 +194,6 @@ void ConnectionStatusChangedHandler(ConnectionStatus status, ConnectionStatusCha edgeHubUnderlyingClient.Verify(c => c.UpdateReportedPropertiesAsync(It.IsAny()), Times.Once); Assert.Equal(6, connectedCallbackCount); Assert.Equal(3, disconnectedCallbackCount); - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeApiClientTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeApiClientTest.cs index 583ddbbb076..b8bdac63bba 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeApiClientTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeApiClientTest.cs @@ -99,6 +99,20 @@ public void GetServiceUriForTargetModuleTest() Assert.Equal(expectedToken, uri.ToString()); } + [Theory] + [MemberData(nameof(GetErrorDetectionData))] + public void ErrorDetectionStrategyTest(Exception ex, bool isTransient) + { + // Arrange + var errorDetectionStrategy = new DeviceScopeApiClient.ErrorDetectionStrategy(); + + // Act + bool isTransientResponse = errorDetectionStrategy.IsTransient(ex); + + // Assert + Assert.Equal(isTransientResponse, isTransient); + } + static IEnumerable GetErrorDetectionData() { yield return new object[] { new ArgumentException(), false }; @@ -116,19 +130,5 @@ static IEnumerable GetErrorDetectionData() yield return new object[] { new DeviceScopeApiException("foo", HttpStatusCode.ServiceUnavailable, "bar"), true }; yield return new object[] { new DeviceScopeApiException("foo", HttpStatusCode.NotImplemented, "bar"), true }; } - - [Theory] - [MemberData(nameof(GetErrorDetectionData))] - public void ErrorDetectionStrategyTest(Exception ex, bool isTransient) - { - // Arrange - var errorDetectionStrategy = new DeviceScopeApiClient.ErrorDetectionStrategy(); - - // Act - bool isTransientResponse = errorDetectionStrategy.IsTransient(ex); - - // Assert - Assert.Equal(isTransientResponse, isTransient); - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeCertificateAuthenticatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeCertificateAuthenticatorTest.cs index 07d34a6d2fb..5da7b7c511c 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeCertificateAuthenticatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeCertificateAuthenticatorTest.cs @@ -4,8 +4,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; - using System.Threading.Tasks; using System.Text; + using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Authenticators; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; @@ -13,14 +13,14 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test using Microsoft.Azure.Devices.Edge.Hub.Core.Identity.Service; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; using Moq; using Xunit; + using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; [Unit] public class DeviceScopeCertificateAuthenticatorTest { - static IAuthenticator UnderlyingAuthenticator = new NullAuthenticator(); + static readonly IAuthenticator UnderlyingAuthenticator = new NullAuthenticator(); [Fact] public void DeviceScopeCertificateAuthenticatorNullArgumentsThrows() @@ -55,19 +55,24 @@ public async Task AuthenticateAsyncWithDeviceThumbprintX509InScopeCacheSucceeds( var deviceScopeIdentitiesCache = new Mock(); IList trustBundle = new List(); - var primaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var primaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == primaryCertificate && c.ClientCertificateChain == primaryClientCertChain); - var secondaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var secondaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == secondaryCertificate && c.ClientCertificateChain == secondaryClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), - ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); @@ -87,19 +92,24 @@ public async Task AuthenticateAsyncWithDeviceThumbprintX509InScopeCacheSucceedsW var deviceScopeIdentitiesCache = new Mock(); IList trustBundle = new List(); - var primaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var primaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == primaryCertificate && c.ClientCertificateChain == primaryClientCertChain); - var secondaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var secondaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == secondaryCertificate && c.ClientCertificateChain == secondaryClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), - ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); @@ -119,19 +129,24 @@ public async Task AuthenticateAsyncWithDeviceThumbprintX509MismatchInScopeCacheF var deviceScopeIdentitiesCache = new Mock(); IList trustBundle = new List(); - var primaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var primaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == primaryCertificate && c.ClientCertificateChain == primaryClientCertChain); - var secondaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var secondaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == secondaryCertificate && c.ClientCertificateChain == secondaryClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(new X509ThumbprintAuthentication("7A57E1E55", "DECAF")), - ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(new X509ThumbprintAuthentication("7A57E1E55", "DECAF")), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); @@ -151,20 +166,25 @@ public async Task AuthenticateAsyncWithDeviceThumbprintX509NotInScopeCacheFails( var deviceScopeIdentitiesCache = new Mock(); IList trustBundle = new List(); - var primaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var primaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == primaryCertificate && c.ClientCertificateChain == primaryClientCertChain); - var secondaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var secondaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == secondaryCertificate && c.ClientCertificateChain == secondaryClientCertChain); // setup identity for another device id - var serviceIdentity = new ServiceIdentity("some_other_device", "1234", new string[0], - new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), - ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + "some_other_device", + "1234", + new string[0], + new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == "some_other_device"), false)).ReturnsAsync(Option.Some(serviceIdentity)); @@ -184,19 +204,25 @@ public async Task AuthenticateAsyncWithDeviceThumbprintX509InconsistentAuthInSco var deviceScopeIdentitiesCache = new Mock(); IList trustBundle = new List(); - var primaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var primaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == primaryCertificate && c.ClientCertificateChain == primaryClientCertChain); - var secondaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var secondaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == secondaryCertificate && c.ClientCertificateChain == secondaryClientCertChain); string key = GetKey(); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(new SymmetricKeyAuthentication(key, GetKey())), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(new SymmetricKeyAuthentication(key, GetKey())), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); @@ -217,17 +243,21 @@ public async Task AuthenticateAsyncWithDeviceCAX509InScopeCacheSucceeds() string deviceId = "MyIssuedTestClient"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.True(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -244,17 +274,21 @@ public async Task AuthenticateAsyncWithDeviceCAX509NotInScopeCacheFails() string someOtherDeviceId = "some other device id"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(someOtherDeviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + someOtherDeviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == someOtherDeviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -270,17 +304,21 @@ public async Task AuthenticateAsyncWithDeviceCAX509ExpiredCertInScopeCacheFails( string deviceId = "MyIssuedTestClient"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -297,17 +335,21 @@ public async Task AuthenticateAsyncWithDeviceCAX509FutureValidCertInScopeCacheFa string deviceId = "MyIssuedTestClient"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -324,17 +366,21 @@ public async Task AuthenticateAsyncWithDeviceCAX509CATrueInScopeCacheFails() string deviceId = "MyIssuedTestClient"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -350,17 +396,21 @@ public async Task AuthenticateAsyncWithMismatchDeviceIDCAX509InScopeCacheFails() string deviceId = "different from CN"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -371,22 +421,26 @@ public async Task AuthenticateAsyncWithEmptyChainDeviceCAX509InScopeCacheFails() var notAfter = DateTime.Now.AddYears(1); var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true); var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false, null, null); - IList issuedClientCertChain = new List() { }; // empty chain supplied + IList issuedClientCertChain = new List() { }; // empty chain supplied IList trustBundle = new List() { caCert }; string deviceId = "different from CN"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -403,17 +457,21 @@ public async Task AuthenticateAsyncWithInvalidChainDeviceCAX509InScopeCacheFails string deviceId = "different from CN"; var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.Id == deviceId) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == deviceId), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } @@ -430,21 +488,29 @@ public async Task AuthenticateAsyncWithModuleThumbprintX509InScopeCacheFails() var deviceScopeIdentitiesCache = new Mock(); IList trustBundle = new List(); - var primaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.ModuleId == moduleId - && i.Id == identity) + var primaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of( + i => i.DeviceId == deviceId && i.ModuleId == moduleId + && i.Id == identity) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == primaryCertificate && c.ClientCertificateChain == primaryClientCertChain); - var secondaryCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.ModuleId == moduleId - && i.Id == identity) + var secondaryCredentials = Mock.Of( + c => + c.Identity == Mock.Of( + i => i.DeviceId == deviceId && i.ModuleId == moduleId + && i.Id == identity) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == secondaryCertificate && c.ClientCertificateChain == secondaryClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, moduleId, "1234", new string[0], - new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), - ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + moduleId, + "1234", + new string[0], + new ServiceAuthentication(new X509ThumbprintAuthentication(primaryCertificate.Thumbprint, secondaryCertificate.Thumbprint)), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == identity), false)).ReturnsAsync(Option.Some(serviceIdentity)); @@ -467,18 +533,24 @@ public async Task AuthenticateAsyncWithModuleCAX509InScopeCacheFails() string identity = FormattableString.Invariant($"{deviceId}/{moduleId}"); var deviceScopeIdentitiesCache = new Mock(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == deviceId && i.ModuleId == moduleId - && i.Id == identity) + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of( + i => i.DeviceId == deviceId && i.ModuleId == moduleId + && i.Id == identity) && c.AuthenticationType == AuthenticationType.X509Cert && c.ClientCertificate == issuedClientCert && c.ClientCertificateChain == issuedClientCertChain); - var serviceIdentity = new ServiceIdentity(deviceId, moduleId, "1234", new string[0], - new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled); + var serviceIdentity = new ServiceIdentity( + deviceId, + moduleId, + "1234", + new string[0], + new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), + ServiceIdentityStatus.Enabled); var authenticator = new DeviceScopeCertificateAuthenticator(deviceScopeIdentitiesCache.Object, UnderlyingAuthenticator, trustBundle, true); deviceScopeIdentitiesCache.Setup(d => d.GetServiceIdentity(It.Is(i => i == identity), false)).ReturnsAsync(Option.Some(serviceIdentity)); - Assert.False(await authenticator.AuthenticateAsync(clientCredentials)); } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeTokenAuthenticatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeTokenAuthenticatorTest.cs index 4002e7c697c..c4379f72c9b 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeTokenAuthenticatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/DeviceScopeTokenAuthenticatorTest.cs @@ -471,7 +471,7 @@ public async Task ValidateUnderlyingAuthenticatorErrorTest() // Act await Assert.ThrowsAsync(() => authenticator.AuthenticateAsync(tokenCredentials)); - // Assert + // Assert Mock.Get(underlyingAuthenticator).VerifyAll(); Mock.Get(underlyingAuthenticator).Verify(u => u.AuthenticateAsync(It.IsAny()), Times.Once); } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/EventHubReceiver.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/EventHubReceiver.cs index fbf6deb0aa8..f0afcc34e1c 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/EventHubReceiver.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/EventHubReceiver.cs @@ -22,9 +22,9 @@ public async Task> GetMessagesFromAllPartitions(DateTime startT foreach (string partition in rtInfo.PartitionIds) { PartitionReceiver partitionReceiver = this.eventHubClient.CreateReceiver( - PartitionReceiver.DefaultConsumerGroupName, - partition, - startTime); + PartitionReceiver.DefaultConsumerGroupName, + partition, + startTime); // Retry a few times to make sure we get all expected messages. for (int i = 0; i < 3; i++) @@ -40,8 +40,10 @@ public async Task> GetMessagesFromAllPartitions(DateTime startT await Task.Delay(TimeSpan.FromSeconds(5)); } } + await partitionReceiver.CloseAsync(); } + return messages; } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/MessageHelper.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/MessageHelper.cs index 3689324d290..a1311c69588 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/MessageHelper.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/MessageHelper.cs @@ -43,6 +43,7 @@ public static IList GenerateMessages(int count) var message = new EdgeMessage(messageBytes, properties, systemProperties); messages.Add(message); } + return messages; } @@ -54,16 +55,17 @@ public static bool CompareMessagesAndEventData(IList messages, IList - m.Properties.ContainsKey("id") && - m.Properties["id"] as string == message.Properties["id"]); + EventData eventData = events.FirstOrDefault( + m => + m.Properties.ContainsKey("id") && + m.Properties["id"] as string == message.Properties["id"]); if (eventData == null || !message.Body.SequenceEqual(eventData.Body.Array)) { return false; } } + return true; } - } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test.csproj index ffb8bb8ea15..6b3a0c4da32 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test.csproj @@ -44,4 +44,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceIdentityHelpersTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceIdentityHelpersTest.cs index 206d4aecf23..33c03612527 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceIdentityHelpersTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceIdentityHelpersTest.cs @@ -14,6 +14,57 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test [Unit] public class ServiceIdentityHelpersTest { + [Theory] + [MemberData(nameof(GetDeviceJson))] + public void DeviceToServiceIdentityTest(string deviceJson) + { + // Arrange + var device = JsonConvert.DeserializeObject(deviceJson); + + // Act + ServiceIdentity serviceIdentity = device.ToServiceIdentity(); + + // Assert + Assert.NotNull(serviceIdentity); + Assert.Equal(device.Id, serviceIdentity.DeviceId); + Assert.False(serviceIdentity.IsModule); + Assert.False(serviceIdentity.ModuleId.HasValue); + Assert.Equal(device.GenerationId, serviceIdentity.GenerationId); + if (device.Capabilities.IotEdge) + { + Assert.True(serviceIdentity.Capabilities.Contains(Constants.IotEdgeIdentityCapability)); + } + else + { + Assert.False(serviceIdentity.Capabilities.Any()); + } + + Assert.Equal(device.Status == DeviceStatus.Enabled ? ServiceIdentityStatus.Enabled : ServiceIdentityStatus.Disabled, serviceIdentity.Status); + + ValidateAuthentication(device.Authentication, serviceIdentity.Authentication); + } + + [Theory] + [MemberData(nameof(GetModuleJson))] + public void ModuleToServiceIdentityTest(string moduleJson) + { + // Arrange + var module = JsonConvert.DeserializeObject(moduleJson); + + // Act + ServiceIdentity serviceIdentity = module.ToServiceIdentity(); + + // Assert + Assert.NotNull(serviceIdentity); + Assert.Equal(module.DeviceId, serviceIdentity.DeviceId); + Assert.Equal(module.Id, serviceIdentity.ModuleId.OrDefault()); + Assert.True(serviceIdentity.IsModule); + Assert.Equal(module.GenerationId, serviceIdentity.GenerationId); + Assert.False(serviceIdentity.Capabilities.Any()); + Assert.Equal(ServiceIdentityStatus.Enabled, serviceIdentity.Status); + ValidateAuthentication(module.Authentication, serviceIdentity.Authentication); + } + static IEnumerable GetDeviceJson() { yield return new object[] @@ -256,57 +307,6 @@ static IEnumerable GetModuleJson() }; } - [Theory] - [MemberData(nameof(GetDeviceJson))] - public void DeviceToServiceIdentityTest(string deviceJson) - { - // Arrange - var device = JsonConvert.DeserializeObject(deviceJson); - - // Act - ServiceIdentity serviceIdentity = device.ToServiceIdentity(); - - // Assert - Assert.NotNull(serviceIdentity); - Assert.Equal(device.Id, serviceIdentity.DeviceId); - Assert.False(serviceIdentity.IsModule); - Assert.False(serviceIdentity.ModuleId.HasValue); - Assert.Equal(device.GenerationId, serviceIdentity.GenerationId); - if (device.Capabilities.IotEdge) - { - Assert.True(serviceIdentity.Capabilities.Contains(Constants.IotEdgeIdentityCapability)); - } - else - { - Assert.False(serviceIdentity.Capabilities.Any()); - } - - Assert.Equal(device.Status == DeviceStatus.Enabled ? ServiceIdentityStatus.Enabled : ServiceIdentityStatus.Disabled, serviceIdentity.Status); - - ValidateAuthentication(device.Authentication, serviceIdentity.Authentication); - } - - [Theory] - [MemberData(nameof(GetModuleJson))] - public void ModuleToServiceIdentityTest(string moduleJson) - { - // Arrange - var module = JsonConvert.DeserializeObject(moduleJson); - - // Act - ServiceIdentity serviceIdentity = module.ToServiceIdentity(); - - // Assert - Assert.NotNull(serviceIdentity); - Assert.Equal(module.DeviceId, serviceIdentity.DeviceId); - Assert.Equal(module.Id, serviceIdentity.ModuleId.OrDefault()); - Assert.True(serviceIdentity.IsModule); - Assert.Equal(module.GenerationId, serviceIdentity.GenerationId); - Assert.False(serviceIdentity.Capabilities.Any()); - Assert.Equal(ServiceIdentityStatus.Enabled, serviceIdentity.Status); - ValidateAuthentication(module.Authentication, serviceIdentity.Authentication); - } - static void ValidateAuthentication(AuthenticationMechanism authenticationMechanism, ServiceAuthentication serviceIdentityAuthentication) { Assert.NotNull(serviceIdentityAuthentication); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceProxyTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceProxyTest.cs index 1ea73b8eced..cf8c4f7f836 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceProxyTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/ServiceProxyTest.cs @@ -156,7 +156,7 @@ public async Task GetServiceIdentitiy_BadRequest_DeviceTest() Option serviceIdentity = await serviceProxy.GetServiceIdentity("d1"); // Assert - Assert.False(serviceIdentity.HasValue); + Assert.False(serviceIdentity.HasValue); } [Fact] @@ -205,7 +205,8 @@ public async Task GetServiceIdentitiy_ModuleTest() { // Arrange IEnumerable devices1 = null; - IEnumerable modules1 = new[] { GetModule("d1", "m1") }; ; + IEnumerable modules1 = new[] { GetModule("d1", "m1") }; + string continuationToken1 = null; var scopeResult1 = new ScopeResult(devices1, modules1, continuationToken1); var deviceScopeApiResult = new Mock(); @@ -225,7 +226,8 @@ public async Task GetServiceIdentitiy_Module_InvalidTest_MultipleResults() { // Arrange IEnumerable devices1 = null; - IEnumerable modules1 = new[] { GetModule("d1", "m1"), GetModule("d1", "m1") }; ; + IEnumerable modules1 = new[] { GetModule("d1", "m1"), GetModule("d1", "m1") }; + string continuationToken1 = null; var scopeResult1 = new ScopeResult(devices1, modules1, continuationToken1); var deviceScopeApiResult = new Mock(); @@ -244,7 +246,8 @@ public async Task GetServiceIdentitiy_Module_InvalidTest_EmptyResult() { // Arrange IEnumerable devices1 = null; - IEnumerable modules1 = new Module[0]; ; + IEnumerable modules1 = new Module[0]; + string continuationToken1 = null; var scopeResult1 = new ScopeResult(devices1, modules1, continuationToken1); var deviceScopeApiResult = new Mock(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenCacheAuthenticatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenCacheAuthenticatorTest.cs index 77a0fcb08b0..8dc6bb5b52a 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenCacheAuthenticatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenCacheAuthenticatorTest.cs @@ -56,7 +56,7 @@ public async Task AuthenticateFromCacheTest() var cloudProxy = Mock.Of(c => c.IsActive && c.OpenAsync() == Task.FromResult(true)); var connectionManager = Mock.Of( c => - c.CreateCloudConnectionAsync(It.IsAny()) == Task.FromResult(Try.Success(cloudProxy))); + c.CreateCloudConnectionAsync(It.IsAny()) == Task.FromResult(Try.Success(cloudProxy))); string iothubHostName = "iothub1.azure.net"; string callerProductInfo = "productInfo"; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenHelperTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenHelperTest.cs index ca0f88e411d..1294ecab03f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenHelperTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TokenHelperTest.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test using System; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; + using TokenHelper = Microsoft.Azure.Devices.Edge.Hub.CloudProxy.TokenHelper; [Unit] public class TokenHelperTest @@ -14,20 +15,20 @@ public void CheckTokenExpiredTest() // Arrange string hostname = "dummy.azure-devices.net"; DateTime expiryTime = DateTime.UtcNow.AddHours(2); - string validToken = TokenHelper.CreateSasToken(hostname, expiryTime); + string validToken = Util.Test.Common.TokenHelper.CreateSasToken(hostname, expiryTime); // Act - DateTime actualExpiryTime = Hub.CloudProxy.TokenHelper.GetTokenExpiry(hostname, validToken); + DateTime actualExpiryTime = TokenHelper.GetTokenExpiry(hostname, validToken); // Assert Assert.True(actualExpiryTime - expiryTime < TimeSpan.FromSeconds(1)); // Arrange expiryTime = DateTime.UtcNow.AddHours(-2); - string expiredToken = TokenHelper.CreateSasToken(hostname, expiryTime); + string expiredToken = Util.Test.Common.TokenHelper.CreateSasToken(hostname, expiryTime); // Act - actualExpiryTime = Hub.CloudProxy.TokenHelper.GetTokenExpiry(hostname, expiredToken); + actualExpiryTime = TokenHelper.GetTokenExpiry(hostname, expiredToken); // Assert Assert.Equal(DateTime.MinValue, actualExpiryTime); @@ -38,10 +39,10 @@ public void GetIsTokenExpiredTest() { // Arrange DateTime tokenExpiry = DateTime.UtcNow.AddYears(1); - string token = TokenHelper.CreateSasToken("azure.devices.net", tokenExpiry); + string token = Util.Test.Common.TokenHelper.CreateSasToken("azure.devices.net", tokenExpiry); // Act - TimeSpan expiryTimeRemaining = Hub.CloudProxy.TokenHelper.GetTokenExpiryTimeRemaining("azure.devices.net", token); + TimeSpan expiryTimeRemaining = TokenHelper.GetTokenExpiryTimeRemaining("azure.devices.net", token); // Assert Assert.True(expiryTimeRemaining - (tokenExpiry - DateTime.UtcNow) < TimeSpan.FromSeconds(1)); @@ -50,8 +51,8 @@ public void GetIsTokenExpiredTest() [Fact] public void GetTokenExpiryBufferSecondsTest() { - string token = TokenHelper.CreateSasToken("azure.devices.net"); - TimeSpan timeRemaining = Hub.CloudProxy.TokenHelper.GetTokenExpiryTimeRemaining("foo.azuredevices.net", token); + string token = Util.Test.Common.TokenHelper.CreateSasToken("azure.devices.net"); + TimeSpan timeRemaining = TokenHelper.GetTokenExpiryTimeRemaining("foo.azuredevices.net", token); Assert.True(timeRemaining > TimeSpan.Zero); } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinCollectionMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinCollectionMessageConverterTest.cs index f3a5ddb48ee..e00d75230e4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinCollectionMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinCollectionMessageConverterTest.cs @@ -75,11 +75,12 @@ public static IEnumerable GetTwinCollectionData() public void ConvertsTwinCollectionsToMqttMessages(TwinCollection collection, string expectedJson) { EdgeMessage expectedMessage = new EdgeMessage.Builder(expectedJson.ToBody()) - .SetSystemProperties(new Dictionary() - { - [SystemProperties.EnqueuedTime] = "", - [SystemProperties.Version] = collection.Version.ToString() - }) + .SetSystemProperties( + new Dictionary() + { + [SystemProperties.EnqueuedTime] = string.Empty, + [SystemProperties.Version] = collection.Version.ToString() + }) .Build(); IMessage actualMessage = new TwinCollectionMessageConverter().ToMessage(collection); Assert.Equal(expectedMessage.Body, actualMessage.Body); @@ -94,7 +95,8 @@ public void ConvertedMessageHasAnEnqueuedTimeProperty() IMessage actualMessage = new TwinCollectionMessageConverter().ToMessage(new TwinCollection()); Assert.InRange( DateTime.Parse(actualMessage.SystemProperties[SystemProperties.EnqueuedTime], null, DateTimeStyles.RoundtripKind), - DateTime.UtcNow.Subtract(new TimeSpan(0, 1, 0)), DateTime.UtcNow); + DateTime.UtcNow.Subtract(new TimeSpan(0, 1, 0)), + DateTime.UtcNow); } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinMessageConverterTest.cs index c07572fa058..9e549925e8d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Test/TwinMessageConverterTest.cs @@ -111,10 +111,11 @@ public static IEnumerable GetTwinData() public void ConvertsTwinMessagesToMqttMessages(Twin twin, string expectedJson) { EdgeMessage expectedMessage = new EdgeMessage.Builder(expectedJson.ToBody()) - .SetSystemProperties(new Dictionary() - { - [SystemProperties.EnqueuedTime] = "" - }) + .SetSystemProperties( + new Dictionary() + { + [SystemProperties.EnqueuedTime] = string.Empty + }) .Build(); IMessage actualMessage = new TwinMessageConverter().ToMessage(twin); Assert.Equal(expectedMessage.Body, actualMessage.Body); @@ -139,7 +140,8 @@ public void ConvertedMessageHasAnEnqueuedTimeProperty() IMessage actualMessage = new TwinMessageConverter().ToMessage(new Twin()); Assert.InRange( DateTime.Parse(actualMessage.SystemProperties[SystemProperties.EnqueuedTime], null, DateTimeStyles.RoundtripKind), - DateTime.UtcNow.Subtract(new TimeSpan(0, 1, 0)), DateTime.UtcNow); + DateTime.UtcNow.Subtract(new TimeSpan(0, 1, 0)), + DateTime.UtcNow); } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/AuthenticatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/AuthenticatorTest.cs index 6118915d529..f4af5af79c8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/AuthenticatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/AuthenticatorTest.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test using System; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Hub.CloudProxy.Authenticators; - using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; @@ -16,6 +15,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test public class AuthenticatorTest { const string TestIotHub = "iothub1.azure.net"; + [Fact] [Unit] public void AuthenticatorConstructorTest() @@ -128,9 +128,10 @@ public async Task Authenticate_X509Identity() var connectionManager = Mock.Of(); var credentialsCache = Mock.Of(); var certificateAuthenticator = Mock.Of(); - var clientCredentials = Mock.Of(c => - c.Identity == Mock.Of(i => i.DeviceId == "my-device") - && c.AuthenticationType == AuthenticationType.X509Cert); + var clientCredentials = Mock.Of( + c => + c.Identity == Mock.Of(i => i.DeviceId == "my-device") + && c.AuthenticationType == AuthenticationType.X509Cert); var tokenAuthenticator = Mock.Of(); Mock.Get(credentialsCache).Setup(cc => cc.Add(clientCredentials)).Returns(Task.CompletedTask); Mock.Get(certificateAuthenticator).Setup(ca => ca.AuthenticateAsync(clientCredentials)).ReturnsAsync(true); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionManagerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionManagerTest.cs index 8b9f4ef40b7..6f1ec468ff9 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionManagerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionManagerTest.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Client; using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Edge.Hub.CloudProxy; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; @@ -84,7 +85,7 @@ public async Task CloudConnectionTest() IClient client2 = GetDeviceClient(); var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(client1) .Returns(client2); @@ -191,7 +192,7 @@ public async Task TestAddRemoveDeviceConnectionTest() IClient client = GetDeviceClient(); var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(client); var credentialsManager = Mock.Of(); @@ -295,7 +296,7 @@ public async Task CreateCloudProxyTest() IClient client2 = GetDeviceClient(); var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(client1) .Returns(client2); @@ -421,8 +422,8 @@ public async Task CloudConnectionUpdateTest() ITokenProvider receivedTokenProvider = null; var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((i, s, t) => receivedTokenProvider = s) + deviceClientProvider.Setup(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((i, s, t) => receivedTokenProvider = s) .Returns(() => GetDeviceClient()); var credentialsCache = Mock.Of(); @@ -475,7 +476,7 @@ public async Task CloudConnectionInvalidUpdateTest() { var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(GetDeviceClient()) .Throws(new UnauthorizedException("connstr2 is invalid!")) .Throws(new UnauthorizedException("connstr2 is invalid!")); @@ -747,7 +748,7 @@ public async Task GetCloudProxyTest() IClient client2 = GetDeviceClient(); var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(client1) .Returns(client2); @@ -801,7 +802,7 @@ public async Task GetMultipleCloudProxiesTest() IClient client2 = GetDeviceClient(); var messageConverterProvider = Mock.Of(); var deviceClientProvider = new Mock(); - deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) + deviceClientProvider.SetupSequence(d => d.Create(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(client1) .Returns(client2); @@ -809,17 +810,17 @@ public async Task GetMultipleCloudProxiesTest() await credentialsCache.Add(module1Credentials); var cloudConnectionProvider = new CloudConnectionProvider( - messageConverterProvider, - 1, - deviceClientProvider.Object, - Option.None(), - Mock.Of(), - Mock.Of(), - credentialsCache, - new ModuleIdentity(IotHubHostName, edgeDeviceId, "$edgeHub"), - TimeSpan.FromMinutes(60), - true, - TimeSpan.FromSeconds(20)); + messageConverterProvider, + 1, + deviceClientProvider.Object, + Option.None(), + Mock.Of(), + Mock.Of(), + credentialsCache, + new ModuleIdentity(IotHubHostName, edgeDeviceId, "$edgeHub"), + TimeSpan.FromMinutes(60), + true, + TimeSpan.FromSeconds(20)); cloudConnectionProvider.BindEdgeHub(Mock.Of()); IConnectionManager connectionManager = new ConnectionManager(cloudConnectionProvider, credentialsCache, GetIdentityProvider()); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionProviderTest.cs index 2ffc51f11e3..316d72be813 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionProviderTest.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionReauthenticatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionReauthenticatorTest.cs index 780eda506a8..13a0985ca8a 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionReauthenticatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ConnectionReauthenticatorTest.cs @@ -59,7 +59,7 @@ public async Task TestConnectionReauthentication() deviceConnectivityManager); connectionReauthenticator.Init(); - // Assert + // Assert connectionManager.Verify(c => c.GetConnectedClients(), Times.Never); await Task.Delay(reauthFrequency + TimeSpan.FromSeconds(1)); @@ -110,7 +110,7 @@ public async Task TestConnectionReauthentication_AuthenticationFailureTest() deviceConnectivityManager); connectionReauthenticator.Init(); - // Assert + // Assert connectionManager.Verify(c => c.GetConnectedClients(), Times.Never); await Task.Delay(reauthFrequency + TimeSpan.FromSeconds(1)); @@ -165,7 +165,7 @@ public async Task TestConnectionReauthentication_MultipleLoopsTest() deviceConnectivityManager); connectionReauthenticator.Init(); - // Assert + // Assert connectionManager.Verify(c => c.GetConnectedClients(), Times.Never); await Task.Delay(reauthFrequency * 2 + TimeSpan.FromSeconds(1)); @@ -202,7 +202,7 @@ public void TestHandleServiceIdentityRemove() deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityRemoved += null, null, "d1"); deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityRemoved += null, null, "d1/m1"); - // Assert + // Assert Assert.NotNull(connectionReauthenticator); connectionManager.Verify(c => c.RemoveDeviceConnection("d1"), Times.Once); connectionManager.Verify(c => c.RemoveDeviceConnection("d1/m1"), Times.Once); @@ -237,7 +237,7 @@ public void TestHandleEdgeHubServiceIdentityRemove() deviceConnectivityManager); deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityRemoved += null, null, "d2/$edgeHub"); - // Assert + // Assert Assert.NotNull(connectionReauthenticator); connectionManager.Verify(c => c.RemoveDeviceConnection("d2/$edgeHub"), Times.Never); authenticator.VerifyAll(); @@ -291,7 +291,7 @@ public void TestHandleServiceIdentityUpdateFailure() deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityUpdated += null, null, deviceServiceIdentity); deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityUpdated += null, null, moduleServiceIdentity); - // Assert + // Assert Assert.NotNull(connectionReauthenticator); connectionManager.Verify(c => c.RemoveDeviceConnection("d1"), Times.Once); connectionManager.Verify(c => c.RemoveDeviceConnection("d1/m1"), Times.Once); @@ -347,7 +347,7 @@ public void TestHandleServiceIdentityUpdateSuccess() deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityUpdated += null, null, deviceServiceIdentity); deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityUpdated += null, null, moduleServiceIdentity); - // Assert + // Assert Assert.NotNull(connectionReauthenticator); connectionManager.Verify(c => c.RemoveDeviceConnection("d1"), Times.Never); connectionManager.Verify(c => c.RemoveDeviceConnection("d1/m1"), Times.Never); @@ -392,7 +392,7 @@ public void TestHandleEdgeHubServiceIdentityUpdateSuccess() deviceConnectivityManager); deviceScopeIdentitiesCache.Raise(d => d.ServiceIdentityUpdated += null, null, edgeHubServiceIdentity); - // Assert + // Assert Assert.NotNull(connectionReauthenticator); connectionManager.Verify(c => c.RemoveDeviceConnection("d1"), Times.Never); connectionManager.Verify(c => c.RemoveDeviceConnection("d1/$edgeHub"), Times.Never); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/CredentialsCacheTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/CredentialsCacheTest.cs index bc5ecfc9cbc..62dc2a79338 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/CredentialsCacheTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/CredentialsCacheTest.cs @@ -39,7 +39,7 @@ public async Task RoundtripTest() [Fact] public async Task GetFromPersistedCacheTest() { - // Arrange + // Arrange var identity1 = Mock.Of(i => i.Id == "d1"); var identity2 = Mock.Of(i => i.Id == "d2/m2"); var creds1 = Mock.Of(c => c.Identity == identity1); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceMessageHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceMessageHandlerTest.cs index 057e875ceca..7f134c21a97 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceMessageHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceMessageHandlerTest.cs @@ -213,11 +213,11 @@ public async Task MessageCompletionMismatchedResponseTest() var connMgr = new Mock(); connMgr.Setup(c => c.AddDeviceConnection(It.IsAny(), It.IsAny())); var identity = Mock.Of(m => m.DeviceId == "device1" && m.ModuleId == "module1" && m.Id == "device1/module1"); - var cloudProxy = new Mock(); + var cloudProxy = new Mock(); var edgeHub = Mock.Of(); var underlyingDeviceProxy = new Mock(); underlyingDeviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask); + .Returns(Task.CompletedTask); connMgr.Setup(c => c.GetCloudConnection(It.IsAny())).Returns(Task.FromResult(Option.Some(cloudProxy.Object))); var deviceMessageHandler = new DeviceMessageHandler(identity, edgeHub, connMgr.Object); deviceMessageHandler.BindDeviceProxy(underlyingDeviceProxy.Object); @@ -243,11 +243,12 @@ public async Task X509DeviceCanSendMessageTest() string lockToken = null; var underlyingDeviceProxy = new Mock(); underlyingDeviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())) - .Callback((m ,i) => - { - messageReceived = true; - lockToken = m.SystemProperties[SystemProperties.LockToken]; - }) + .Callback( + (m, i) => + { + messageReceived = true; + lockToken = m.SystemProperties[SystemProperties.LockToken]; + }) .Returns(Task.CompletedTask); connMgr.Setup(c => c.GetCloudConnection(It.IsAny())).Returns(Task.FromResult(cloudProxy)); var deviceMessageHandler = new DeviceMessageHandler(identity, edgeHub, connMgr.Object); @@ -286,7 +287,7 @@ public async Task ProcessDesiredPropertiesUpdateSubscription() Assert.NotNull(sentMessage); Assert.Equal(correlationId, sentMessage.SystemProperties[SystemProperties.CorrelationId]); Assert.Equal("200", sentMessage.SystemProperties[SystemProperties.StatusCode]); - edgeHub.VerifyAll(); + edgeHub.VerifyAll(); } [Fact] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceScopeIdentitiesCacheTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceScopeIdentitiesCacheTest.cs index 65d1c158733..12b0c108b29 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceScopeIdentitiesCacheTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/DeviceScopeIdentitiesCacheTest.cs @@ -48,7 +48,7 @@ public async Task InitializeFromStoreTest() [Fact] public async Task RefreshCacheTest() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthentication = new ServiceAuthentication(ServiceAuthenticationType.None); Func si1 = () => new ServiceIdentity("d1", "1234", Enumerable.Empty(), serviceAuthentication, ServiceIdentityStatus.Enabled); @@ -138,7 +138,7 @@ public async Task RefreshCacheTest() [Fact] public async Task RefreshCacheWithRefreshRequestTest() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthentication = new ServiceAuthentication(ServiceAuthenticationType.None); Func si1 = () => new ServiceIdentity("d1", "1234", Enumerable.Empty(), serviceAuthentication, ServiceIdentityStatus.Enabled); @@ -279,7 +279,7 @@ public async Task RefreshCacheWithRefreshRequestTest() [Fact] public async Task RefreshServiceIdentityTest_Device() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthenticationNone = new ServiceAuthentication(ServiceAuthenticationType.None); var serviceAuthenticationSas = new ServiceAuthentication(new SymmetricKeyAuthentication(GetKey(), GetKey())); @@ -342,7 +342,7 @@ public async Task RefreshServiceIdentityTest_Device() [Fact] public async Task RefreshServiceIdentityTest_List() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthenticationNone = new ServiceAuthentication(ServiceAuthenticationType.None); var serviceAuthenticationSas = new ServiceAuthentication(new SymmetricKeyAuthentication(GetKey(), GetKey())); @@ -404,7 +404,7 @@ public async Task RefreshServiceIdentityTest_List() [Fact] public async Task RefreshServiceIdentityTest_Module() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthenticationNone = new ServiceAuthentication(ServiceAuthenticationType.None); var serviceAuthenticationSas = new ServiceAuthentication(new SymmetricKeyAuthentication(GetKey(), GetKey())); @@ -467,7 +467,7 @@ public async Task RefreshServiceIdentityTest_Module() [Fact] public async Task GetServiceIdentityTest_Device() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthenticationNone = new ServiceAuthentication(ServiceAuthenticationType.None); var serviceAuthenticationSas = new ServiceAuthentication(new SymmetricKeyAuthentication(GetKey(), GetKey())); @@ -532,7 +532,7 @@ public async Task GetServiceIdentityTest_Device() [Fact] public async Task GetServiceIdentityTest_Module() { - // Arrange + // Arrange var store = new EntityStore(new InMemoryDbStore(), "cache"); var serviceAuthenticationNone = new ServiceAuthentication(ServiceAuthenticationType.None); var serviceAuthenticationSas = new ServiceAuthentication(new SymmetricKeyAuthentication(GetKey(), GetKey())); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConfigTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConfigTest.cs index bf38112f19d..4fad41bf941 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConfigTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConfigTest.cs @@ -15,7 +15,7 @@ public class EdgeHubConfigTest [Fact] public void ConstructorHappyPath() { - // Arrange + // Arrange IEnumerable<(string Name, string Value, Route route)> routes = Enumerable.Empty<(string Name, string Value, Route route)>(); var snfConfig = new StoreAndForwardConfiguration(1000); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConnectionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConnectionTest.cs index 490e30d8e52..6cac1368c60 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConnectionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/EdgeHubConnectionTest.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Hub.CloudProxy; - using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Hub.Core.Routing; @@ -95,7 +94,7 @@ public async Task HandleMethodInvocationBadInputTest() var edgeHubConnection = new EdgeHubConnection(edgeHubIdentity, twinManager, routeFactory, twinCollectionMessageConverter, twinMessageConverter, versionInfo, deviceScopeIdentitiesCache.Object); string correlationId = Guid.NewGuid().ToString(); - var requestPayload = new [] + var requestPayload = new[] { "d1", "d2" diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IdentityFactoryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IdentityFactoryTest.cs index e70b4059335..5386c43c826 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IdentityFactoryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IdentityFactoryTest.cs @@ -52,14 +52,6 @@ public void GetWithConnectionStringTest_Module() Assert.Equal(moduleId, identity.ModuleId); } - static string GetRandomString(int length) - { - var rand = new Random(); - const string Chars = "abcdefghijklmnopqrstuvwxyz"; - return new string(Enumerable.Repeat(Chars, length) - .Select(s => s[rand.Next(s.Length)]).ToArray()); - } - [Fact] public void GetSasIdentityTest() { @@ -126,5 +118,14 @@ public void GetX509IdentityTest() Assert.Equal($"{callerProductInfo} customDeviceClient1", identityTry2.ProductInfo); Assert.Equal(AuthenticationType.X509Cert, identityTry1.AuthenticationType); } + + static string GetRandomString(int length) + { + var rand = new Random(); + const string Chars = "abcdefghijklmnopqrstuvwxyz"; + return new string( + Enumerable.Repeat(Chars, length) + .Select(s => s[rand.Next(s.Length)]).ToArray()); + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/InvokeMethodHandlerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/InvokeMethodHandlerTest.cs index f9bb6c8b19c..cc1e2a9c34c 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/InvokeMethodHandlerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/InvokeMethodHandlerTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test [Unit] public class InvokeMethodHandlerTest { - [Fact] + [Fact] public async Task InvokeMethodTest() { // Arrange @@ -43,7 +43,7 @@ public async Task InvokeMethodTest() // Assert Assert.NotNull(response); Assert.Equal(response.CorrelationId, request.CorrelationId); - Assert.Equal(response.HttpStatusCode, HttpStatusCode.OK); + Assert.Equal(response.HttpStatusCode, HttpStatusCode.OK); Assert.Null(response.Data); Assert.Equal(response.Status, 200); Assert.False(response.Exception.HasValue); @@ -111,7 +111,7 @@ public async Task InvokeMethodDeviceNotConnectedTest() Task invokeMethodTask = invokeMethodHandler.InvokeMethod(request); await Task.Delay(TimeSpan.FromSeconds(2)); - // Assert + // Assert Assert.False(invokeMethodTask.IsCompleted); // Act @@ -155,7 +155,7 @@ public async Task InvokeMethodNoSubscriptionTest() Task invokeMethodTask = invokeMethodHandler.InvokeMethod(request); await Task.Delay(TimeSpan.FromSeconds(2)); - // Assert + // Assert Assert.False(invokeMethodTask.IsCompleted); // Act diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IotHubConnectionExceptionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IotHubConnectionExceptionTest.cs index b45526aa18f..8cc067d730b 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IotHubConnectionExceptionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/IotHubConnectionExceptionTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test { using System; - using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageConverterProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageConverterProviderTest.cs index e77d9c501fb..50473503bd8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageConverterProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageConverterProviderTest.cs @@ -9,19 +9,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test [Unit] public class MessageConverterProviderTest { - class TestMessageConverter : IMessageConverter - { - public IMessage ToMessage(int sourceMessage) - { - throw new NotImplementedException(); - } - - public int FromMessage(IMessage message) - { - throw new NotImplementedException(); - } - } - [Fact] public void ProvidesAConverterForTheGivenType() { @@ -43,5 +30,18 @@ public void ThrowsWhenItDoesNotHaveTheRequestedProvider() var fn = new Func>(() => provider.Get()); Assert.Throws(fn); } + + class TestMessageConverter : IMessageConverter + { + public IMessage ToMessage(int sourceMessage) + { + throw new NotImplementedException(); + } + + public int FromMessage(IMessage message) + { + throw new NotImplementedException(); + } + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageTest.cs index 67abd4ab426..4bd75325bb3 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/MessageTest.cs @@ -5,18 +5,20 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test using System.Collections.Generic; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; - using Message = Microsoft.Azure.Devices.Edge.Hub.Core.EdgeMessage; + using Message = EdgeMessage; public class MessageTest { - static readonly Message Message1 = new Message.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }).Build(); - static readonly Message Message2 = new Message.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }).Build(); - static readonly Message Message3 = new Message.Builder(new byte[] { 2, 3, 1 }).SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }).Build(); - static readonly Message Message4 = new Message.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key", "value" }, { "key2", "value2" } }).Build(); - static readonly Message Message5 = new Message.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key", "value" } }).Build(); - static readonly Message Message6 = new Message.Builder(new byte[] { 1, 2, 3 }) + static readonly Message Message1 = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }).Build(); + static readonly Message Message2 = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }).Build(); + static readonly Message Message3 = new EdgeMessage.Builder(new byte[] { 2, 3, 1 }).SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }).Build(); + static readonly Message Message4 = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key", "value" }, { "key2", "value2" } }).Build(); + static readonly Message Message5 = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).SetProperties(new Dictionary { { "key", "value" } }).Build(); + + static readonly Message Message6 = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }) .SetProperties(new Dictionary { { "key1", "value1" }, { "key2", "value2" } }) .SetSystemProperties(new Dictionary { { "sys1", "value1" } }).Build(); + static readonly Message Message7 = new Message(new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, new Dictionary { { "sys1", "value1" } }); static readonly Message Message8 = new Message(new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, new Dictionary { { "sys1", "value2" } }); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/Microsoft.Azure.Devices.Edge.Hub.Core.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/Microsoft.Azure.Devices.Edge.Hub.Core.Test.csproj index c2b4c7d5eb6..30b01328101 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/Microsoft.Azure.Devices.Edge.Hub.Core.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/Microsoft.Azure.Devices.Edge.Hub.Core.Test.csproj @@ -40,4 +40,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ServiceIdentityTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ServiceIdentityTest.cs index 1fbc52f110b..973cc01387c 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ServiceIdentityTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/ServiceIdentityTest.cs @@ -32,8 +32,8 @@ static IEnumerable GetEqualityTestData() yield return new object[] { - new ServiceIdentity("d1", "1234", new List{"123", "iotEdge"}, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), - new ServiceIdentity("d1", "1234", new List{"123", "iotEdge"}, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), + new ServiceIdentity("d1", "1234", new List { "123", "iotEdge" }, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), + new ServiceIdentity("d1", "1234", new List { "123", "iotEdge" }, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), true }; @@ -60,16 +60,16 @@ static IEnumerable GetEqualityTestData() // Module identities - Equal yield return new object[] -{ + { new ServiceIdentity("d1", "m1", "1234", new List(), new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), new ServiceIdentity("d1", "m1", "1234", new List(), new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), true -}; + }; yield return new object[] { - new ServiceIdentity("d1", "m1", "1234", new List{"123", "iotEdge"}, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), - new ServiceIdentity("d1", "m1", "1234", new List{"123", "iotEdge"}, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), + new ServiceIdentity("d1", "m1", "1234", new List { "123", "iotEdge" }, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), + new ServiceIdentity("d1", "m1", "1234", new List { "123", "iotEdge" }, new ServiceAuthentication(ServiceAuthenticationType.CertificateAuthority), ServiceIdentityStatus.Enabled), true }; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/TwinManagerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/TwinManagerTest.cs index 40e11380d30..69eda521a22 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/TwinManagerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/TwinManagerTest.cs @@ -2,10 +2,14 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test { using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Hub.CloudProxy; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; + using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; @@ -14,17 +18,13 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; - using System.Linq; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; [Unit] public class TwinManagerTest { - Option> twinStore; - IMessageConverter twinCollectionMessageConverter; - IMessageConverter twinMessageConverter; + readonly Option> twinStore; + readonly IMessageConverter twinCollectionMessageConverter; + readonly IMessageConverter twinMessageConverter; public TwinManagerTest() { @@ -72,7 +72,18 @@ public async void GetTwinWhenCloudOnlineTwinNotStoredSuccess() bool storeMiss = false; // Act - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert Assert.Equal(storeHit, false); @@ -82,7 +93,14 @@ public async void GetTwinWhenCloudOnlineTwinNotStoredSuccess() await twinManager.GetTwinAsync(deviceId); // Assert - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); Assert.Equal(storeHit, true); } @@ -115,7 +133,14 @@ public async void GetTwinWhenCloudOfflineSuccess() // Assert Assert.Equal(getTwinCalled, true); Assert.Equal(received.Body, twinMessage.Body); - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); Assert.Equal(storeHit, true); } @@ -187,7 +212,14 @@ public async void UpdateDesiredPropertiesWhenTwinStoredVersionPlus1Success() // Act await twinManager.UpdateDesiredPropertiesAsync(deviceId, collectionMessage); TwinInfo patched = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { patched = t; return Task.CompletedTask; }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + patched = t; + return Task.CompletedTask; + }, + () => Task.FromResult(null)); // Assert Assert.Equal(patched.Twin.Properties.Desired.ToJson(), collection.ToJson()); @@ -231,7 +263,15 @@ public async void UpdateDesiredPropertiesWhenTwinNotStoredVersionPlus1Success() // Act await twinManager.UpdateDesiredPropertiesAsync(deviceId, collectionMessage); TwinInfo updated = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; updated = t; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + updated = t; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert Assert.Equal(getTwinCalled, true); @@ -249,8 +289,7 @@ public async void UpdateDesiredPropertiesPassthroughSuccess() var mockDeviceProxy = new Mock(); Option deviceProxy = Option.Some(mockDeviceProxy.Object); mockDeviceProxy.Setup(t => t.OnDesiredPropertyUpdates(It.IsAny())) - .Callback((m) => - { received = this.twinCollectionMessageConverter.FromMessage(m); }) + .Callback((m) => { received = this.twinCollectionMessageConverter.FromMessage(m); }) .Returns(Task.CompletedTask); var connectionManager = new Mock(); @@ -343,7 +382,18 @@ public async void UpdateReportedPropertiesWhenCloudOnlineTwinNotStoredSuccess() bool storeHit = false; // Act - find the twin - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert - verify that the twin is not in the store Assert.Equal(storeHit, false); @@ -358,21 +408,41 @@ public async void UpdateReportedPropertiesWhenCloudOnlineTwinNotStoredSuccess() // Assert - verify that the twin was fetched storeMiss = false; storeHit = false; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); Assert.Equal(storeHit, true); // Assert - verify that the local twin's reported properties updated. // verify that the local patch of reported properties was not updated. TwinInfo retrieved = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { retrieved = t; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + retrieved = t; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); Assert.Equal(storeMiss, false); - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(retrieved.Twin.Properties.Reported.ToJson()), - JsonConvert.DeserializeObject(collection.ToJson()))); - - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(retrieved.ReportedPropertiesPatch.ToJson()), - JsonConvert.DeserializeObject((new TwinCollection()).ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(retrieved.Twin.Properties.Reported.ToJson()), + JsonConvert.DeserializeObject(collection.ToJson()))); + + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(retrieved.ReportedPropertiesPatch.ToJson()), + JsonConvert.DeserializeObject(new TwinCollection().ToJson()))); } [Fact] @@ -407,7 +477,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTwinNotStoredSuccess() bool storeHit = false; // Act - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert Assert.Equal(storeHit, false); @@ -416,26 +497,52 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTwinNotStoredSuccess() // Act await twinManager.UpdateReportedPropertiesAsync(deviceId, collectionMessage1); TwinInfo cached1 = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; cached1 = t; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + cached1 = t; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert Assert.Equal(storeHit, true); Assert.Equal(cached1.Twin, null); - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(cached1.ReportedPropertiesPatch.ToJson()), - JsonConvert.DeserializeObject(collection1.ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(cached1.ReportedPropertiesPatch.ToJson()), + JsonConvert.DeserializeObject(collection1.ToJson()))); // Act await twinManager.UpdateReportedPropertiesAsync(deviceId, collectionMessage2); TwinInfo cached2 = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; cached2 = t; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + cached2 = t; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert Assert.Equal(storeHit, true); Assert.NotNull(cached2.Twin); - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(cached2.ReportedPropertiesPatch.ToJson()), - JsonConvert.DeserializeObject(collection2.ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(cached2.ReportedPropertiesPatch.ToJson()), + JsonConvert.DeserializeObject(collection2.ToJson()))); } [Fact] @@ -469,7 +576,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTwinStoredSuccess() bool storeHit = false; // Act - check if twin is in the cache - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert - verify that twin is not in the cache Assert.Equal(storeHit, false); @@ -481,7 +599,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTwinStoredSuccess() // Assert - verify that the twin was fetched storeMiss = false; storeHit = false; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); Assert.Equal(storeHit, true); // Arrange - make the cloud offline @@ -505,13 +634,22 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTwinStoredSuccess() // Assert - verify that the twin's reported properties was updated and that the patch was stored TwinInfo retrieved = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { retrieved = t; return Task.FromResult(t); }, () => Task.FromResult(null)); - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(retrieved.Twin.Properties.Reported.ToJson()), - JsonConvert.DeserializeObject(merged.ToJson()))); - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(retrieved.ReportedPropertiesPatch.ToJson()), - JsonConvert.DeserializeObject(patch.ToJson()))); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + retrieved = t; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(retrieved.Twin.Properties.Reported.ToJson()), + JsonConvert.DeserializeObject(merged.ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(retrieved.ReportedPropertiesPatch.ToJson()), + JsonConvert.DeserializeObject(patch.ToJson()))); } [Fact] @@ -547,7 +685,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineMalformedPropertiesThr bool storeHit = false; // Act - check if twin is in the cache - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert - verify that twin is not in the cache Assert.Equal(storeHit, false); @@ -559,7 +708,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineMalformedPropertiesThr // Assert - verify that the twin was fetched storeMiss = false; storeHit = false; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); Assert.Equal(storeHit, true); // Arrange - make the cloud offline @@ -612,7 +772,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTooLargeCollectionThro bool storeHit = false; // Act - check if twin is in the cache - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert - verify that twin is not in the cache Assert.Equal(storeHit, false); @@ -624,7 +795,18 @@ public async void UpdateReportedPropertiesWhenCloudOfflineTooLargeCollectionThro // Assert - verify that the twin was fetched storeMiss = false; storeHit = false; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); Assert.Equal(storeHit, true); // Arrange - make the cloud offline @@ -721,7 +903,18 @@ public async void GetTwinDoesNotOverwriteSavedReportedPropertiesSuccess() bool storeMiss = false; // Act - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => { storeMiss = true; return Task.FromResult(null); }); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => + { + storeMiss = true; + return Task.FromResult(null); + }); // Assert - verify that the twin is not in the store Assert.Equal(storeHit, false); @@ -731,7 +924,14 @@ public async void GetTwinDoesNotOverwriteSavedReportedPropertiesSuccess() await twinManager.GetTwinAsync(deviceId); // Assert - verify that the twin is in the store - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { storeHit = true; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + storeHit = true; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); Assert.Equal(storeHit, true); // Arrange - update reported properties when offline @@ -749,25 +949,42 @@ public async void GetTwinDoesNotOverwriteSavedReportedPropertiesSuccess() // Act - find the twin and reported property patch TwinInfo cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t; return Task.CompletedTask; }, () => Task.CompletedTask); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t; + return Task.CompletedTask; + }, + () => Task.CompletedTask); // Assert - verify that the patch and the twin were updated - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(cached.Twin.Properties.Reported.ToJson()), - JsonConvert.DeserializeObject(collection.ToJson()))); - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(cached.ReportedPropertiesPatch.ToJson()), - JsonConvert.DeserializeObject(collection.ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(cached.Twin.Properties.Reported.ToJson()), + JsonConvert.DeserializeObject(collection.ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(cached.ReportedPropertiesPatch.ToJson()), + JsonConvert.DeserializeObject(collection.ToJson()))); // Act - get twin so that the local twin gets updated await twinManager.GetTwinAsync(deviceId); // Assert - verify that the twin was updated but patch was not - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t; return Task.CompletedTask; }, () => Task.CompletedTask); - - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(cached.ReportedPropertiesPatch.ToJson()), - JsonConvert.DeserializeObject(collection.ToJson()))); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t; + return Task.CompletedTask; + }, + () => Task.CompletedTask); + + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(cached.ReportedPropertiesPatch.ToJson()), + JsonConvert.DeserializeObject(collection.ToJson()))); } [Fact] @@ -878,7 +1095,14 @@ public async void GetTwinRejectsLowerVersionTwinsSuccess() // Act - call get twin to cache twin IMessage received = await twinManager.GetTwinAsync(deviceId); Twin cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t.Twin; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t.Twin; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify version of twin matches what we setup Assert.Equal(cached.Version, twin.Version); @@ -893,7 +1117,14 @@ public async void GetTwinRejectsLowerVersionTwinsSuccess() received = await twinManager.GetTwinAsync(deviceId); cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t.Twin; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t.Twin; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify version of twin matches original version Assert.Equal(cached.Version, 32); @@ -929,7 +1160,14 @@ public async void GetTwinDoesNotGeneratesDesiredPropertyUpdateIfNotSusbribedSucc // Act - call get twin to cache twin IMessage received = await twinManager.GetTwinAsync(deviceId); Twin cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t.Twin; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t.Twin; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify version of twin matches what we setup Assert.Equal(cached.Properties.Desired.Version, twin.Properties.Desired.Version); @@ -944,7 +1182,14 @@ public async void GetTwinDoesNotGeneratesDesiredPropertyUpdateIfNotSusbribedSucc TwinInfo twinInfo = await twinManager.GetTwinInfoWhenCloudOnlineAsync(deviceId, mockCloudProxy.Object, true); cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t.Twin; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t.Twin; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify version of twin matches new version and device not subsribed Assert.Equal(cached.Properties.Desired.Version, 33); @@ -996,7 +1241,14 @@ public async void DesiredPropertyFetchesTwinWithCallbackSuccess() await twinManager.GetTwinAsync(deviceId); Twin cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t.Twin; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t.Twin; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify that twin is cached Assert.Equal(cached.Properties.Desired.Version, 32); @@ -1021,9 +1273,10 @@ public async void DesiredPropertyFetchesTwinWithCallbackSuccess() await twinManager.UpdateDesiredPropertiesAsync(deviceId, twinCollectionMessage); // Assert - verify that get twin cached the latest twin and called the desired property update callback - Assert.True(JToken.DeepEquals( - JsonConvert.DeserializeObject(receivedPatch.ToJson()), - JsonConvert.DeserializeObject(twin.Properties.Desired.ToJson()))); + Assert.True( + JToken.DeepEquals( + JsonConvert.DeserializeObject(receivedPatch.ToJson()), + JsonConvert.DeserializeObject(twin.Properties.Desired.ToJson()))); } [Fact] @@ -1059,7 +1312,14 @@ public async void ConnectionReestablishedReportedPropertiesSyncSuccess() await twinManager.UpdateReportedPropertiesAsync(deviceId, reportedMessage); TwinInfo cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify cloud was called and twin manager has collective patch Assert.Equal(callbackReceived, true); @@ -1077,7 +1337,14 @@ public async void ConnectionReestablishedReportedPropertiesSyncSuccess() await twinManager.UpdateReportedPropertiesAsync(deviceId, reportedMessage); cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify that twin manager did not attempt to call cloud because there is already // a patch @@ -1088,11 +1355,12 @@ public async void ConnectionReestablishedReportedPropertiesSyncSuccess() var onlineReported = new TwinCollection(); callbackReceived = false; mockCloudProxy.Setup(t => t.UpdateReportedPropertiesAsync(It.IsAny())) - .Callback(m => - { - onlineReported = this.twinCollectionMessageConverter.FromMessage(m); - callbackReceived = true; - }) + .Callback( + m => + { + onlineReported = this.twinCollectionMessageConverter.FromMessage(m); + callbackReceived = true; + }) .Returns(Task.CompletedTask); var identity = Mock.Of(i => i.Id == deviceId); @@ -1106,7 +1374,14 @@ public async void ConnectionReestablishedReportedPropertiesSyncSuccess() } cached = null; - await twinManager.ExecuteOnTwinStoreResultAsync(deviceId, t => { cached = t; return Task.FromResult(t); }, () => Task.FromResult(null)); + await twinManager.ExecuteOnTwinStoreResultAsync( + deviceId, + t => + { + cached = t; + return Task.FromResult(t); + }, + () => Task.FromResult(null)); // Assert - verify that cloud proxy got the collective patch and that the collective patch is cleared Assert.Equal(onlineReported.ToJson(), reported.ToJson()); @@ -1129,11 +1404,12 @@ public async void ConnectionReestablishedGetTwinWithDesiredPropertyUpdateSuccess bool callbackReceived = false; var mockDeviceProxy = new Mock(); mockDeviceProxy.Setup(t => t.OnDesiredPropertyUpdates(It.IsAny())) - .Callback(m => - { - receivedPatch = this.twinCollectionMessageConverter.FromMessage(m); - callbackReceived = true; - }) + .Callback( + m => + { + receivedPatch = this.twinCollectionMessageConverter.FromMessage(m); + callbackReceived = true; + }) .Returns(Task.CompletedTask); Option deviceProxy = Option.Some(mockDeviceProxy.Object); @@ -1214,10 +1490,7 @@ public async void ConnectionReestablishedDoesNotSyncReportedPropertiesWhenEmptyS bool reportedReceived = false; mockCloudProxy.Setup(t => t.UpdateReportedPropertiesAsync(It.IsAny())) - .Callback(m => - { - reportedReceived = true; - }) + .Callback(m => { reportedReceived = true; }) .Returns(Task.CompletedTask); var identity = Mock.Of(i => i.Id == "blah"); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/CloudMessageProcessorTests.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/CloudMessageProcessorTests.cs index dff1fbbf212..ad0c80ebb70 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/CloudMessageProcessorTests.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/CloudMessageProcessorTests.cs @@ -34,18 +34,18 @@ public void BasicTest() IProcessor cloudMessageProcessor = cloudEndpoint.CreateProcessor(); Assert.Equal(cloudEndpoint, cloudMessageProcessor.Endpoint); - Assert.False(cloudMessageProcessor.ErrorDetectionStrategy.IsTransient(new Exception())); + Assert.False(cloudMessageProcessor.ErrorDetectionStrategy.IsTransient(new Exception())); } [Fact] [Unit] public async Task ProcessAsyncTest() { - Core.IMessageConverter routingMessageConverter = new RoutingMessageConverter(); + Core.IMessageConverter routingMessageConverter = new RoutingMessageConverter(); string cloudEndpointId = Guid.NewGuid().ToString(); var cloudProxyMock = new Mock(); - cloudProxyMock.Setup(c => c.SendMessageAsync(It.IsAny())) + cloudProxyMock.Setup(c => c.SendMessageAsync(It.IsAny())) .Returns(Task.CompletedTask); cloudProxyMock.SetupGet(p => p.IsActive).Returns(true); @@ -74,9 +74,10 @@ public async Task ProcessAsyncTest() Task> GetCloudProxy(string id) { - return Task.FromResult(id.Equals(device1Id) - ? Option.Some(cloudProxyMock.Object) - : Option.None()); + return Task.FromResult( + id.Equals(device1Id) + ? Option.Some(cloudProxyMock.Object) + : Option.None()); } var cloudEndpoint = new CloudEndpoint(cloudEndpointId, GetCloudProxy, routingMessageConverter); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/EndpointFactoryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/EndpointFactoryTest.cs index 2b533b704d3..cd41f6ad22e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/EndpointFactoryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/EndpointFactoryTest.cs @@ -94,7 +94,7 @@ public void TestCreateDuplicateFunctionsEndpoint() [InlineData(null)] public void TestCreateSystemEndpointInvalidCases(string systemEndpoint) { - Assert.Throws(() =>this.endpointFactory.CreateSystemEndpoint(systemEndpoint)); + Assert.Throws(() => this.endpointFactory.CreateSystemEndpoint(systemEndpoint)); } [Theory] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/ModuleEndpointTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/ModuleEndpointTest.cs index 672ae640537..95a705e0ebd 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/ModuleEndpointTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/ModuleEndpointTest.cs @@ -12,6 +12,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test.Routing using Microsoft.Azure.Devices.Routing.Core; using Moq; using Xunit; + using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; [Unit] @@ -68,7 +69,7 @@ public async Task ModuleMessageProcessor_ProcessAsyncTest() { Core.IMessageConverter routingMessageConverter = new RoutingMessageConverter(); var deviceProxy = new Mock(); - deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); deviceProxy.Setup(d => d.IsActive).Returns(true); IReadOnlyDictionary deviceSubscriptions = new ReadOnlyDictionary( new Dictionary @@ -85,7 +86,7 @@ public async Task ModuleMessageProcessor_ProcessAsyncTest() var moduleEndpoint = new ModuleEndpoint($"{moduleId}/{moduleEndpointAddress}", moduleId, moduleEndpointAddress, connectionManager.Object, routingMessageConverter); IProcessor moduleMessageProcessor = moduleEndpoint.CreateProcessor(); - ISinkResult sinkResult = await moduleMessageProcessor.ProcessAsync(routingMessage ,CancellationToken.None); + ISinkResult sinkResult = await moduleMessageProcessor.ProcessAsync(routingMessage, CancellationToken.None); Assert.True(sinkResult.Succeeded.Contains(routingMessage)); } @@ -94,7 +95,7 @@ public async Task ModuleMessageProcessor_ProcessAsyncTest_InactiveDeviceProxy() { Core.IMessageConverter routingMessageConverter = new RoutingMessageConverter(); var deviceProxy = new Mock(); - deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); deviceProxy.Setup(d => d.IsActive).Returns(false); var routingMessage = Mock.Of(); string moduleId = "device1/module1"; @@ -143,7 +144,7 @@ public async Task ModuleMessageProcessor_ProcessAsyncTest_NoSubscription() { Core.IMessageConverter routingMessageConverter = new RoutingMessageConverter(); var deviceProxy = new Mock(); - deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); deviceProxy.Setup(d => d.IsActive).Returns(true); var routingMessage = Mock.Of(); string moduleId = "device1/module1"; @@ -169,7 +170,7 @@ public async Task ModuleMessageProcessor_ProcessAsyncTest_InactiveSubscription() { Core.IMessageConverter routingMessageConverter = new RoutingMessageConverter(); var deviceProxy = new Mock(); - deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); deviceProxy.Setup(d => d.IsActive).Returns(true); var routingMessage = Mock.Of(); string moduleId = "device1/module1"; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingEdgeHubTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingEdgeHubTest.cs index e486cd95bb1..8a559ed3d3b 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingEdgeHubTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingEdgeHubTest.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test.Routing using System; using System.Collections.Generic; using System.Net; + using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Hub.Core.Cloud; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; @@ -15,8 +16,10 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test.Routing using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Moq; using Xunit; - using IMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; - using Message = Microsoft.Azure.Devices.Edge.Hub.Core.EdgeMessage; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Core.Constants; + using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; + using Message = EdgeMessage; + using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; [Unit] public class RoutingEdgeHubTest @@ -45,10 +48,10 @@ public async Task ProcessDeviceMessageBatch_ConvertsMessages() Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); // Create mock message converter to generate a message with source matching the route - var message = Mock.Of(); + var message = Mock.Of(); Mock.Get(message).SetupGet(m => m.MessageSource).Returns(() => TelemetryMessageSource.Instance); - var messageConverter = Mock.Of>(); - Mock.Get(messageConverter).Setup(mc => mc.FromMessage(It.IsAny())).Returns(message); + var messageConverter = Mock.Of>(); + Mock.Get(messageConverter).Setup(mc => mc.FromMessage(It.IsAny())).Returns(message); // Create mock for IConnectionManager var connectionManager = Mock.Of(); @@ -67,11 +70,11 @@ public async Task ProcessDeviceMessageBatch_ConvertsMessages() Mock.Of()); var identity = new Mock(); identity.SetupGet(id => id.Id).Returns("something"); - EdgeMessage[] messages = { new Message.Builder(new byte[0]).Build() }; + EdgeMessage[] messages = { new EdgeMessage.Builder(new byte[0]).Build() }; await routingEdgeHub.ProcessDeviceMessageBatch(identity.Object, messages); // Verify Expectation - Mock.Get(endpointExecutor).Verify(e => e.Invoke(It.IsAny()), Times.Once); + Mock.Get(endpointExecutor).Verify(e => e.Invoke(It.IsAny()), Times.Once); } [Fact] @@ -109,7 +112,7 @@ public async Task EdgeHubChecksMessageSize() var messageConverter = new RoutingMessageConverter(); - Message badMessage = new Message.Builder(new byte[300 * 1024]).Build(); + Message badMessage = new EdgeMessage.Builder(new byte[300 * 1024]).Build(); var routingEdgeHub = new RoutingEdgeHub( router, @@ -122,10 +125,10 @@ public async Task EdgeHubChecksMessageSize() await Assert.ThrowsAsync(() => routingEdgeHub.ProcessDeviceMessage(identity.Object, badMessage)); - string badString = System.Text.Encoding.UTF8.GetString(new byte[300 * 1024], 0, 300 * 1024); + string badString = Encoding.UTF8.GetString(new byte[300 * 1024], 0, 300 * 1024); var badProperties = new Dictionary { ["toolong"] = badString }; - badMessage = new Message.Builder(new byte[1]).SetProperties(badProperties).Build(); + badMessage = new EdgeMessage.Builder(new byte[1]).SetProperties(badProperties).Build(); await Assert.ThrowsAsync(() => routingEdgeHub.ProcessDeviceMessage(identity.Object, badMessage)); @@ -157,14 +160,14 @@ public async Task GetTwinForwardsToTwinManager() var routerConfig = new RouterConfig(new[] { route }); Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); - var messageConverter = Mock.Of>(); + var messageConverter = Mock.Of>(); var connectionManager = Mock.Of(); var twinManager = new Mock(); - var message = Mock.Of(); + var message = Mock.Of(); twinManager.Setup(t => t.GetTwinAsync(It.IsAny())).Returns(Task.FromResult(message)); var routingEdgeHub = new RoutingEdgeHub(router, messageConverter, connectionManager, twinManager.Object, "testEdgeDevice", Mock.Of(), Mock.Of()); - Core.IMessage received = await routingEdgeHub.GetTwinAsync("*"); + IMessage received = await routingEdgeHub.GetTwinAsync("*"); twinManager.Verify(x => x.GetTwinAsync("*"), Times.Once); Assert.Equal(message, received); @@ -193,12 +196,12 @@ public async Task UpdateDesiredPropertiesForwardsToTwinManager() var routerConfig = new RouterConfig(new[] { route }); Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); - var messageConverter = Mock.Of>(); + var messageConverter = Mock.Of>(); var connectionManager = Mock.Of(); var twinManager = new Mock(); - var message = Mock.Of(); - Core.IMessage received = new Message.Builder(new byte[0]).Build(); - twinManager.Setup(t => t.UpdateDesiredPropertiesAsync(It.IsAny(), It.IsAny())).Callback((s, m) => received = message).Returns(Task.CompletedTask); + var message = Mock.Of(); + IMessage received = new EdgeMessage.Builder(new byte[0]).Build(); + twinManager.Setup(t => t.UpdateDesiredPropertiesAsync(It.IsAny(), It.IsAny())).Callback((s, m) => received = message).Returns(Task.CompletedTask); var routingEdgeHub = new RoutingEdgeHub(router, messageConverter, connectionManager, twinManager.Object, "testEdgeDevice", Mock.Of(), Mock.Of()); await routingEdgeHub.UpdateDesiredPropertiesAsync("*", message); @@ -231,7 +234,7 @@ public async Task InvokeMethodAsyncTest() Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); // Create mock message converter to generate a message with source matching the route - var messageConverter = Mock.Of>(); + var messageConverter = Mock.Of>(); // Mock of twin manager var twinManager = Mock.Of(); @@ -267,9 +270,9 @@ public async Task InvokeMethodAsyncTest() Assert.False(responseTask.IsCompleted); // Arrange - Message message = new Message.Builder(new byte[0]).Build(); - message.Properties[Core.SystemProperties.CorrelationId] = methodRequest.CorrelationId; - message.Properties[Core.SystemProperties.StatusCode] = "200"; + Message message = new EdgeMessage.Builder(new byte[0]).Build(); + message.Properties[SystemProperties.CorrelationId] = methodRequest.CorrelationId; + message.Properties[SystemProperties.StatusCode] = "200"; // Act await deviceMessageHandler.ProcessMethodResponseAsync(message); @@ -304,7 +307,7 @@ public async Task InvokeMethodNoSubscriptionTest() Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); // Create mock message converter to generate a message with source matching the route - var messageConverter = Mock.Of>(); + var messageConverter = Mock.Of>(); var methodRequest = new DirectMethodRequest("device1/module1", "shutdown", null, TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(10)); // Mock of twin manager @@ -374,7 +377,7 @@ public async Task InvokeMethodLateSubscriptionTest() Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); // Create mock message converter to generate a message with source matching the route - var messageConverter = Mock.Of>(); + var messageConverter = Mock.Of>(); var methodRequest = new DirectMethodRequest("device1/module1", "shutdown", null, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(20)); // Mock of twin manager @@ -400,9 +403,9 @@ public async Task InvokeMethodLateSubscriptionTest() var underlyingDeviceProxy = new Mock(); // Arrange - Message message = new Message.Builder(new byte[0]).Build(); - message.Properties[Core.SystemProperties.CorrelationId] = methodRequest.CorrelationId; - message.Properties[Core.SystemProperties.StatusCode] = "200"; + Message message = new EdgeMessage.Builder(new byte[0]).Build(); + message.Properties[SystemProperties.CorrelationId] = methodRequest.CorrelationId; + message.Properties[SystemProperties.StatusCode] = "200"; underlyingDeviceProxy.Setup(d => d.InvokeMethodAsync(It.IsAny())) .Callback(() => deviceMessageHandler.ProcessMethodResponseAsync(message)) @@ -448,7 +451,7 @@ public async Task AddEdgeSystemPropertiesTest() var routerConfig = new RouterConfig(new[] { route }); Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); - var messageConverter = Mock.Of>(); + var messageConverter = Mock.Of>(); // Create mock for IConnectionManager var connectionManager = Mock.Of(); @@ -460,24 +463,24 @@ public async Task AddEdgeSystemPropertiesTest() // Test Scenario var routingEdgeHub = new RoutingEdgeHub(router, messageConverter, connectionManager, twinManager, edgeDeviceId, Mock.Of(), Mock.Of()); - Message clientMessage1 = new Message.Builder(new byte[0]).Build(); - clientMessage1.SystemProperties[Core.SystemProperties.ConnectionDeviceId] = edgeDeviceId; + Message clientMessage1 = new EdgeMessage.Builder(new byte[0]).Build(); + clientMessage1.SystemProperties[SystemProperties.ConnectionDeviceId] = edgeDeviceId; routingEdgeHub.AddEdgeSystemProperties(clientMessage1); - Assert.True(clientMessage1.SystemProperties.ContainsKey(Core.SystemProperties.EdgeHubOriginInterface)); - Assert.True(clientMessage1.SystemProperties.ContainsKey(Core.SystemProperties.EdgeMessageId)); - Assert.Equal(Core.Constants.InternalOriginInterface, clientMessage1.SystemProperties[Core.SystemProperties.EdgeHubOriginInterface]); + Assert.True(clientMessage1.SystemProperties.ContainsKey(SystemProperties.EdgeHubOriginInterface)); + Assert.True(clientMessage1.SystemProperties.ContainsKey(SystemProperties.EdgeMessageId)); + Assert.Equal(Constants.InternalOriginInterface, clientMessage1.SystemProperties[SystemProperties.EdgeHubOriginInterface]); - Message clientMessage2 = new Message.Builder(new byte[0]).Build(); - clientMessage2.SystemProperties[Core.SystemProperties.ConnectionDeviceId] = "downstreamDevice"; + Message clientMessage2 = new EdgeMessage.Builder(new byte[0]).Build(); + clientMessage2.SystemProperties[SystemProperties.ConnectionDeviceId] = "downstreamDevice"; routingEdgeHub.AddEdgeSystemProperties(clientMessage2); - Assert.True(clientMessage2.SystemProperties.ContainsKey(Core.SystemProperties.EdgeHubOriginInterface)); - Assert.True(clientMessage2.SystemProperties.ContainsKey(Core.SystemProperties.EdgeMessageId)); - Assert.Equal(Core.Constants.DownstreamOriginInterface, clientMessage2.SystemProperties[Core.SystemProperties.EdgeHubOriginInterface]); + Assert.True(clientMessage2.SystemProperties.ContainsKey(SystemProperties.EdgeHubOriginInterface)); + Assert.True(clientMessage2.SystemProperties.ContainsKey(SystemProperties.EdgeMessageId)); + Assert.Equal(Constants.DownstreamOriginInterface, clientMessage2.SystemProperties[SystemProperties.EdgeHubOriginInterface]); - Message clientMessage3 = new Message.Builder(new byte[0]).Build(); + Message clientMessage3 = new EdgeMessage.Builder(new byte[0]).Build(); routingEdgeHub.AddEdgeSystemProperties(clientMessage3); - Assert.False(clientMessage3.SystemProperties.ContainsKey(Core.SystemProperties.EdgeHubOriginInterface)); - Assert.True(clientMessage3.SystemProperties.ContainsKey(Core.SystemProperties.EdgeMessageId)); + Assert.False(clientMessage3.SystemProperties.ContainsKey(SystemProperties.EdgeHubOriginInterface)); + Assert.True(clientMessage3.SystemProperties.ContainsKey(SystemProperties.EdgeMessageId)); } [Fact] @@ -727,7 +730,7 @@ public async Task ProcessSubscriptionsOnDeviceConnected() deviceIdentity, moduleIdentity }; - + IReadOnlyDictionary device1Subscriptions = new Dictionary() { [DeviceSubscription.Methods] = true, @@ -740,21 +743,24 @@ public async Task ProcessSubscriptionsOnDeviceConnected() [DeviceSubscription.ModuleMessages] = true }; - var device1CloudProxy = Mock.Of(dc => dc.SetupDesiredPropertyUpdatesAsync() == Task.CompletedTask - && dc.SetupCallMethodAsync() == Task.CompletedTask); + var device1CloudProxy = Mock.Of( + dc => dc.SetupDesiredPropertyUpdatesAsync() == Task.CompletedTask + && dc.SetupCallMethodAsync() == Task.CompletedTask); Mock.Get(device1CloudProxy).SetupGet(d => d.IsActive).Returns(true); var module1CloudProxy = Mock.Of(mc => mc.SetupCallMethodAsync() == Task.CompletedTask && mc.IsActive); - var invokeMethodHandler = Mock.Of(m => - m.ProcessInvokeMethodSubscription(d1) == Task.CompletedTask - && m.ProcessInvokeMethodSubscription(m1) == Task.CompletedTask); + var invokeMethodHandler = Mock.Of( + m => + m.ProcessInvokeMethodSubscription(d1) == Task.CompletedTask + && m.ProcessInvokeMethodSubscription(m1) == Task.CompletedTask); - var connectionManager = Mock.Of(c => - c.GetConnectedClients() == connectedClients - && c.GetSubscriptions(d1) == Option.Some(device1Subscriptions) - && c.GetSubscriptions(m1) == Option.Some(module1Subscriptions) - && c.GetCloudConnection(d1) == Task.FromResult(Option.Some(device1CloudProxy)) - && c.GetCloudConnection(m1) == Task.FromResult(Option.Some(module1CloudProxy))); + var connectionManager = Mock.Of( + c => + c.GetConnectedClients() == connectedClients + && c.GetSubscriptions(d1) == Option.Some(device1Subscriptions) + && c.GetSubscriptions(m1) == Option.Some(module1Subscriptions) + && c.GetCloudConnection(d1) == Task.FromResult(Option.Some(device1CloudProxy)) + && c.GetCloudConnection(m1) == Task.FromResult(Option.Some(module1CloudProxy))); var endpoint = new Mock("myId"); var endpointExecutor = Mock.Of(); @@ -770,7 +776,7 @@ public async Task ProcessSubscriptionsOnDeviceConnected() var edgeHub = new RoutingEdgeHub( router, - Mock.Of>(), + Mock.Of>(), connectionManager, Mock.Of(), "ed1", @@ -807,7 +813,7 @@ static async Task GetTestEdgeHub(IConnectionManager connectionMa Router router = await Router.CreateAsync("myRouter", "myIotHub", routerConfig, endpointExecutorFactory); var edgeHub = new RoutingEdgeHub( router, - Mock.Of>(), + Mock.Of>(), connectionManager, Mock.Of(), "ed1", diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingMessageConverterTest.cs index 8726803fda7..47540b4a507 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingMessageConverterTest.cs @@ -10,10 +10,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test.Routing using Moq; using Newtonsoft.Json; using Xunit; - using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; using IRoutingMessage = Microsoft.Azure.Devices.Routing.Core.IMessage; using RoutingMessage = Microsoft.Azure.Devices.Routing.Core.Message; - using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; public class RoutingMessageConverterTest { @@ -92,7 +90,8 @@ public static IEnumerable GetRoutingMessages() var routingMessage1 = new RoutingMessage(TelemetryMessageSource.Instance, messageBytes, properties, systemProperties); routingMessages.Add(new object[] { routingMessage1 }); - var routingMessage2 = new RoutingMessage(TwinChangeEventMessageSource.Instance, + var routingMessage2 = new RoutingMessage( + TwinChangeEventMessageSource.Instance, messageBytes, properties, systemProperties, @@ -113,39 +112,54 @@ public static IEnumerable GetMessageSourceSystemProperties() { var theoryData = new List(); - theoryData.Add(new object[] { - new Dictionary + theoryData.Add( + new object[] { - [SystemProperties.MessageType] = Constants.TwinChangeNotificationMessageType - }, - TwinChangeEventMessageSource.Instance}); - - theoryData.Add(new object[] { - new Dictionary + new Dictionary + { + [SystemProperties.MessageType] = Constants.TwinChangeNotificationMessageType + }, + TwinChangeEventMessageSource.Instance + }); + + theoryData.Add( + new object[] { - [SystemProperties.ConnectionModuleId] = "module1", - [SystemProperties.OutputName] = "output1" - }, - ModuleMessageSource.Create("module1", "output1")}); - - theoryData.Add(new object[] { - new Dictionary + new Dictionary + { + [SystemProperties.ConnectionModuleId] = "module1", + [SystemProperties.OutputName] = "output1" + }, + ModuleMessageSource.Create("module1", "output1") + }); + + theoryData.Add( + new object[] { - [SystemProperties.ConnectionModuleId] = "module1", - [SystemProperties.OutputName] = "output1" - }, - ModuleMessageSource.Create("module1", "output1")}); - - theoryData.Add(new object[] { - new Dictionary + new Dictionary + { + [SystemProperties.ConnectionModuleId] = "module1", + [SystemProperties.OutputName] = "output1" + }, + ModuleMessageSource.Create("module1", "output1") + }); + + theoryData.Add( + new object[] { - [SystemProperties.ConnectionModuleId] = "module1", - }, - ModuleMessageSource.Create("module1")}); - - theoryData.Add(new object[] { - new Dictionary(), - TelemetryMessageSource.Instance}); + new Dictionary + { + [SystemProperties.ConnectionModuleId] = "module1", + }, + ModuleMessageSource.Create("module1") + }); + + theoryData.Add( + new object[] + { + new Dictionary(), + TelemetryMessageSource.Instance + }); return theoryData; } @@ -155,7 +169,7 @@ public static IEnumerable GetMessageSourceSystemProperties() [MemberData(nameof(GetInvalidHubMessages))] public void TestFromMessageErrorCases(IMessage inputMessage, Type exceptionType) { - Core.IMessageConverter messageConverter = new RoutingMessageConverter(); + IMessageConverter messageConverter = new RoutingMessageConverter(); Assert.Throws(exceptionType, () => messageConverter.FromMessage(inputMessage)); } @@ -164,18 +178,19 @@ public void TestFromMessageErrorCases(IMessage inputMessage, Type exceptionType) [MemberData(nameof(GetInvalidRoutingMessages))] public void TestToMessageErrorCases(IRoutingMessage inputMessage, Type exceptionType) { - Core.IMessageConverter messageConverter = new RoutingMessageConverter(); + IMessageConverter messageConverter = new RoutingMessageConverter(); Assert.Throws(exceptionType, () => messageConverter.ToMessage(inputMessage)); } [Theory] [Unit] [MemberData(nameof(GetHubMessagesData))] - public void TestFromMessageCases(byte[] messageBytes, + public void TestFromMessageCases( + byte[] messageBytes, IDictionary properties, IDictionary systemProperties) { - Core.IMessageConverter messageConverter = new RoutingMessageConverter(); + IMessageConverter messageConverter = new RoutingMessageConverter(); IMessage inputMessage = new EdgeMessage(messageBytes, properties, systemProperties); IRoutingMessage routingMessage = messageConverter.FromMessage(inputMessage); @@ -202,7 +217,7 @@ public void TestFromMessageCases(byte[] messageBytes, [MemberData(nameof(GetRoutingMessages))] public void TestToMessageCases(IRoutingMessage routingMessage) { - Core.IMessageConverter messageConverter = new RoutingMessageConverter(); + IMessageConverter messageConverter = new RoutingMessageConverter(); IMessage message = messageConverter.ToMessage(routingMessage); Assert.Equal(routingMessage.Body, message.Body); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingTest.cs index cce5ac2cbf2..50bbf409005 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/routing/RoutingTest.cs @@ -22,8 +22,9 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test.Routing using Moq; using Newtonsoft.Json; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Core.Constants; using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; - using Message = Microsoft.Azure.Devices.Edge.Hub.Core.EdgeMessage; + using Message = EdgeMessage; using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; [Integration] @@ -31,8 +32,6 @@ public class RoutingTest { static readonly Random Rand = new Random(); - static TimeSpan GetSleepTime(int baseSleepSecs = 10) => TimeSpan.FromSeconds(baseSleepSecs + Rand.Next(0, 10)); - [Fact] public async Task RouteToCloudTest() { @@ -404,6 +403,8 @@ public async Task TestRoutingTwinChangeNotificationFromModule() Assert.True(module1.HasReceivedTwinChangeNotification()); } + static TimeSpan GetSleepTime(int baseSleepSecs = 10) => TimeSpan.FromSeconds(baseSleepSecs + Rand.Next(0, 10)); + static async Task<(IEdgeHub, IConnectionManager)> SetupEdgeHub(IEnumerable routes, IoTHub iotHub, string edgeDeviceId) { string iotHubName = "testHub"; @@ -442,6 +443,97 @@ public async Task TestRoutingTwinChangeNotificationFromModule() return (edgeHub, connectionManager); } + static IMessage GetMessage() + { + byte[] messageBody = Encoding.UTF8.GetBytes("Message body"); + var properties = new Dictionary() + { + { "Prop1", "Val1" }, + { "Prop2", "Val2" } + }; + + var systemProperties = new Dictionary + { + { SystemProperties.MessageId, Guid.NewGuid().ToString() } + }; + return new Message(messageBody, properties, systemProperties); + } + + static List GetMessages() + { + var messages = new List(); + for (int i = 0; i < 10; i++) + { + messages.Add(GetMessage()); + } + + return messages; + } + + static IMessage GetReportedPropertiesMessage() + { + var twinCollection = new TwinCollection(); + twinCollection["Status"] = "running"; + twinCollection["ElapsedTime"] = "0.5"; + byte[] messageBody = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(twinCollection)); + return new EdgeMessage.Builder(messageBody).Build(); + } + + static IClientCredentials SetupDeviceIdentity(string deviceId) => + new TokenCredentials(new DeviceIdentity("iotHub", deviceId), Guid.NewGuid().ToString(), string.Empty, false); + + static IClientCredentials SetupModuleCredentials(string moduleId, string deviceId) => + new TokenCredentials(new ModuleIdentity("iotHub", deviceId, moduleId), Guid.NewGuid().ToString(), string.Empty, false); + + class IoTHub + { + public List ReceivedMessages { get; } = new List(); + + public bool HasReceivedMessages(IEnumerable messages) => messages.All(m => this.HasReceivedMessage(m)); + + public bool HasReceivedMessage(IMessage message) => this.ReceivedMessages.Any( + m => + m.SystemProperties[SystemProperties.MessageId] == message.SystemProperties[SystemProperties.MessageId]); + + public bool HasReceivedTwinChangeNotification() => this.ReceivedMessages.Any( + m => + m.SystemProperties[SystemProperties.MessageType] == Constants.TwinChangeNotificationMessageType); + } + + class TestDevice + { + readonly IDeviceListener deviceListener; + readonly IDeviceIdentity deviceIdentity; + + TestDevice(IDeviceIdentity deviceIdentity, IDeviceListener deviceListener) + { + this.deviceIdentity = deviceIdentity; + this.deviceListener = deviceListener; + } + + public static async Task Create(string deviceId, IEdgeHub edgeHub, IConnectionManager connectionManager) + { + IClientCredentials deviceCredentials = SetupDeviceIdentity(deviceId); + Try cloudProxy = await connectionManager.CreateCloudConnectionAsync(deviceCredentials); + Assert.True(cloudProxy.Success); + var deviceProxy = Mock.Of(); + var deviceListener = new DeviceMessageHandler(deviceCredentials.Identity, edgeHub, connectionManager); + deviceListener.BindDeviceProxy(deviceProxy); + return new TestDevice(deviceCredentials.Identity as IDeviceIdentity, deviceListener); + } + + public Task SendMessages(IEnumerable messages) => Task.WhenAll(messages.Select(m => this.SendMessage(m))); + + public Task SendMessage(IMessage message) + { + message.SystemProperties[SystemProperties.ConnectionDeviceId] = this.deviceIdentity.DeviceId; + return this.deviceListener.ProcessDeviceMessageAsync(message); + } + + public Task UpdateReportedProperties(IMessage reportedPropertiesMessage) => + this.deviceListener.UpdateReportedPropertiesAsync(reportedPropertiesMessage, Guid.NewGuid().ToString()); + } + class TestModule { readonly IDeviceListener deviceListener; @@ -469,11 +561,12 @@ public static async Task Create(string deviceId, string moduleId, st var receivedMessages = new List(); var deviceProxy = new Mock(); deviceProxy.Setup(d => d.SendMessageAsync(It.IsAny(), It.Is(e => inputEndpointIds.Contains(e)))) - .Callback((m, e) => - { - receivedMessages.Add(m); - deviceListener.ProcessMessageFeedbackAsync(m.SystemProperties[SystemProperties.LockToken], FeedbackStatus.Complete).Wait(); - }) + .Callback( + (m, e) => + { + receivedMessages.Add(m); + deviceListener.ProcessMessageFeedbackAsync(m.SystemProperties[SystemProperties.LockToken], FeedbackStatus.Complete).Wait(); + }) .Returns(Task.CompletedTask); deviceProxy.SetupGet(d => d.IsActive).Returns(true); deviceListener.BindDeviceProxy(deviceProxy.Object); @@ -506,102 +599,16 @@ public Task SendMessage(IMessage message) public bool HasReceivedMessages(IEnumerable messages) => messages.All(m => this.HasReceivedMessage(m)); - public bool HasReceivedMessage(IMessage message) => this.receivedMessages.Any(m => - m.SystemProperties[SystemProperties.MessageId] == message.SystemProperties[SystemProperties.MessageId]); - - public Task UpdateReportedProperties(IMessage reportedPropertiesMessage) => - this.deviceListener.UpdateReportedPropertiesAsync(reportedPropertiesMessage, Guid.NewGuid().ToString()); - - public bool HasReceivedTwinChangeNotification() => this.receivedMessages.Any(m => - m.SystemProperties[SystemProperties.MessageType] == Core.Constants.TwinChangeNotificationMessageType); - } - - class TestDevice - { - readonly IDeviceListener deviceListener; - readonly IDeviceIdentity deviceIdentity; - - TestDevice(IDeviceIdentity deviceIdentity, IDeviceListener deviceListener) - { - this.deviceIdentity = deviceIdentity; - this.deviceListener = deviceListener; - } - - public static async Task Create(string deviceId, IEdgeHub edgeHub, IConnectionManager connectionManager) - { - IClientCredentials deviceCredentials = SetupDeviceIdentity(deviceId); - Try cloudProxy = await connectionManager.CreateCloudConnectionAsync(deviceCredentials); - Assert.True(cloudProxy.Success); - var deviceProxy = Mock.Of(); - var deviceListener = new DeviceMessageHandler(deviceCredentials.Identity, edgeHub, connectionManager); - deviceListener.BindDeviceProxy(deviceProxy); - return new TestDevice(deviceCredentials.Identity as IDeviceIdentity, deviceListener); - } - - public Task SendMessages(IEnumerable messages) => Task.WhenAll(messages.Select(m => this.SendMessage(m))); - - public Task SendMessage(IMessage message) - { - message.SystemProperties[SystemProperties.ConnectionDeviceId] = this.deviceIdentity.DeviceId; - return this.deviceListener.ProcessDeviceMessageAsync(message); - } + public bool HasReceivedMessage(IMessage message) => this.receivedMessages.Any( + m => + m.SystemProperties[SystemProperties.MessageId] == message.SystemProperties[SystemProperties.MessageId]); public Task UpdateReportedProperties(IMessage reportedPropertiesMessage) => this.deviceListener.UpdateReportedPropertiesAsync(reportedPropertiesMessage, Guid.NewGuid().ToString()); - } - - class IoTHub - { - public List ReceivedMessages { get; } = new List(); - - public bool HasReceivedMessages(IEnumerable messages) => messages.All(m => this.HasReceivedMessage(m)); - - public bool HasReceivedMessage(IMessage message) => this.ReceivedMessages.Any(m => - m.SystemProperties[SystemProperties.MessageId] == message.SystemProperties[SystemProperties.MessageId]); - - public bool HasReceivedTwinChangeNotification() => this.ReceivedMessages.Any(m => - m.SystemProperties[SystemProperties.MessageType] == Core.Constants.TwinChangeNotificationMessageType); - } - - static IMessage GetMessage() - { - byte[] messageBody = Encoding.UTF8.GetBytes("Message body"); - var properties = new Dictionary() - { - { "Prop1", "Val1" }, - { "Prop2", "Val2" } - }; - - var systemProperties = new Dictionary - { - { SystemProperties.MessageId, Guid.NewGuid().ToString() } - }; - return new Message(messageBody, properties, systemProperties); - } - - static List GetMessages() - { - var messages = new List(); - for (int i = 0; i < 10; i++) - { - messages.Add(GetMessage()); - } - return messages; - } - static IMessage GetReportedPropertiesMessage() - { - var twinCollection = new TwinCollection(); - twinCollection["Status"] = "running"; - twinCollection["ElapsedTime"] = "0.5"; - byte[] messageBody = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(twinCollection)); - return new Message.Builder(messageBody).Build(); + public bool HasReceivedTwinChangeNotification() => this.receivedMessages.Any( + m => + m.SystemProperties[SystemProperties.MessageType] == Constants.TwinChangeNotificationMessageType); } - - static IClientCredentials SetupDeviceIdentity(string deviceId) => - new TokenCredentials(new DeviceIdentity("iotHub", deviceId), Guid.NewGuid().ToString(), string.Empty, false); - - static IClientCredentials SetupModuleCredentials(string moduleId, string deviceId) => - new TokenCredentials(new ModuleIdentity("iotHub", deviceId, moduleId), Guid.NewGuid().ToString(), string.Empty, false); } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/storage/MessageStoreTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/storage/MessageStoreTest.cs index 37e9aad0a97..1d7b3c1005a 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/storage/MessageStoreTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Core.Test/storage/MessageStoreTest.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Core.Test.Storage using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Xunit; + using SystemProperties = Microsoft.Azure.Devices.Edge.Hub.Core.SystemProperties; [Integration] public class MessageStoreTest @@ -52,7 +53,7 @@ public async Task BasicTest(long initialCheckpointOffset) Assert.Equal(1000, batchItemsAsList.Count()); for (int j = 0; j < 1000; j++) { - Assert.Equal((((i * 1000) + j) * 2).ToString(), batchItemsAsList.ElementAt(j).SystemProperties[Core.SystemProperties.MessageId]); + Assert.Equal((((i * 1000) + j) * 2).ToString(), batchItemsAsList.ElementAt(j).SystemProperties[SystemProperties.MessageId]); } } @@ -63,7 +64,7 @@ public async Task BasicTest(long initialCheckpointOffset) Assert.Equal(1000, batchItemsAsList2.Count()); for (int j = 0; j < 1000; j++) { - Assert.Equal((((i * 1000) + j) * 2 + 1).ToString(), batchItemsAsList2.ElementAt(j).SystemProperties[Core.SystemProperties.MessageId]); + Assert.Equal((((i * 1000) + j) * 2 + 1).ToString(), batchItemsAsList2.ElementAt(j).SystemProperties[SystemProperties.MessageId]); } } } @@ -233,12 +234,12 @@ public async Task MessageStoreAddRemoveEndpointTest() for (int i = 0; i < 10; i++) { - Assert.Equal($"{i}", batchItemsAsList.ElementAt(i).SystemProperties[Core.SystemProperties.MessageId]); + Assert.Equal($"{i}", batchItemsAsList.ElementAt(i).SystemProperties[SystemProperties.MessageId]); } // Remove await messageStore.RemoveEndpoint("module1"); - + // Assert await Assert.ThrowsAsync(() => messageStore.Add("module1", this.GetMessage(0))); Assert.Throws(() => messageStore.GetMessageIterator("module1")); @@ -261,19 +262,59 @@ public async Task MessageStoreAddRemoveEndpointTest() for (int i = 20; i < 30; i++) { - Assert.Equal($"{i}", batchItemsAsList.ElementAt(i - 20).SystemProperties[Core.SystemProperties.MessageId]); + Assert.Equal($"{i}", batchItemsAsList.ElementAt(i - 20).SystemProperties[SystemProperties.MessageId]); } } + [Fact] + public void MessageWrapperRoundtripTest() + { + var properties = new Dictionary + { + ["Prop1"] = "PropVal1", + ["Prop2"] = "PropVal2" + }; + + var systemProperties = new Dictionary + { + [Devices.Routing.Core.SystemProperties.CorrelationId] = Guid.NewGuid().ToString(), + [Devices.Routing.Core.SystemProperties.DeviceId] = "device1", + [Devices.Routing.Core.SystemProperties.MessageId] = Guid.NewGuid().ToString() + }; + + byte[] body = "Test Message Body".ToBody(); + var enqueueTime = new DateTime(2017, 11, 20, 01, 02, 03); + var dequeueTime = new DateTime(2017, 11, 20, 02, 03, 04); + + IMessage message = new Message( + TelemetryMessageSource.Instance, + body, + properties, + systemProperties, + 100, + enqueueTime, + dequeueTime); + var messageWrapper = new MessageStore.MessageWrapper(message, DateTime.UtcNow, 3); + + byte[] messageWrapperBytes = messageWrapper.ToBytes(); + var retrievedMesssageWrapper = messageWrapperBytes.FromBytes(); + + Assert.NotNull(retrievedMesssageWrapper); + Assert.Equal(messageWrapper.TimeStamp, retrievedMesssageWrapper.TimeStamp); + Assert.Equal(messageWrapper.RefCount, retrievedMesssageWrapper.RefCount); + Assert.Equal(messageWrapper.Message, retrievedMesssageWrapper.Message); + } + IMessage GetMessage(int i) { - return new Message(TelemetryMessageSource.Instance, + return new Message( + TelemetryMessageSource.Instance, $"Test Message {i} Body".ToBody(), new Dictionary(), new Dictionary { - [Core.SystemProperties.EdgeMessageId] = Guid.NewGuid().ToString(), - [Core.SystemProperties.MessageId] = i.ToString() + [SystemProperties.EdgeMessageId] = Guid.NewGuid().ToString(), + [SystemProperties.MessageId] = i.ToString() }); } @@ -292,7 +333,7 @@ IMessage GetMessage(int i) { var dbStoreProvider = new InMemoryDbStoreProvider(); IStoreProvider storeProvider = new StoreProvider(dbStoreProvider); - + IEntityStore checkpointUnderlyingStore = storeProvider.GetEntityStore($"Checkpoint{Guid.NewGuid().ToString()}"); if (initialCheckpointOffset >= 0) { @@ -306,44 +347,5 @@ IMessage GetMessage(int i) await messageStore.AddEndpoint("module2"); return (messageStore, checkpointStore); } - - [Fact] - public void MessageWrapperRoundtripTest() - { - var properties = new Dictionary - { - ["Prop1"] = "PropVal1", - ["Prop2"] = "PropVal2" - }; - - var systemProperties = new Dictionary - { - [SystemProperties.CorrelationId] = Guid.NewGuid().ToString(), - [SystemProperties.DeviceId] = "device1", - [SystemProperties.MessageId] = Guid.NewGuid().ToString() - }; - - byte[] body = "Test Message Body".ToBody(); - var enqueueTime = new DateTime(2017, 11, 20, 01, 02, 03); - var dequeueTime = new DateTime(2017, 11, 20, 02, 03, 04); - - IMessage message = new Message( - TelemetryMessageSource.Instance, - body, - properties, - systemProperties, - 100, - enqueueTime, - dequeueTime); - var messageWrapper = new MessageStore.MessageWrapper(message, DateTime.UtcNow, 3); - - byte[] messageWrapperBytes = messageWrapper.ToBytes(); - var retrievedMesssageWrapper = messageWrapperBytes.FromBytes(); - - Assert.NotNull(retrievedMesssageWrapper); - Assert.Equal(messageWrapper.TimeStamp, retrievedMesssageWrapper.TimeStamp); - Assert.Equal(messageWrapper.RefCount, retrievedMesssageWrapper.RefCount); - Assert.Equal(messageWrapper.Message, retrievedMesssageWrapper.Message); - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Cloud2DeviceTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Cloud2DeviceTest.cs index e32fabc165f..5b87a581147 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Cloud2DeviceTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Cloud2DeviceTest.cs @@ -19,13 +19,14 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test [TestCaseOrderer("Microsoft.Azure.Devices.Edge.Util.Test.PriorityOrderer", "Microsoft.Azure.Devices.Edge.Util.Test")] public class Cloud2DeviceTest : IClassFixture { - static readonly TimeSpan ClockSkewAdjustment = TimeSpan.FromSeconds(35); const string MessagePropertyName = "property1"; const string DeviceNamePrefix = "E2E_c2d_"; + static readonly TimeSpan ClockSkewAdjustment = TimeSpan.FromSeconds(35); - [Theory, TestPriority(101)] + [Theory] + [TestPriority(101)] [InlineData(TransportType.Mqtt_Tcp_Only)] - //[InlineData(TransportType.Mqtt_WebSocket_Only)] // Disabled: need a valid server cert for WebSocket to work + // [InlineData(TransportType.Mqtt_WebSocket_Only)] // Disabled: need a valid server cert for WebSocket to work public async void Receive_C2D_SingleMessage_ShouldSucceed(TransportType transportType) { // Arrange @@ -58,7 +59,8 @@ public async void Receive_C2D_SingleMessage_ShouldSucceed(TransportType transpor } } - [Fact, TestPriority(102)] + [Fact] + [TestPriority(102)] public async void Receive_C2D_OfflineSingleMessage_ShouldSucceed() { // Arrange @@ -82,7 +84,7 @@ public async void Receive_C2D_OfflineSingleMessage_ShouldSucceed() await Task.Delay(TimeSpan.FromSeconds(20)); // Act - //Send message before device is listening + // Send message before device is listening Message message = this.CreateMessage(out string payload); await serviceClient.SendAsync(deviceName, message); @@ -98,7 +100,8 @@ public async void Receive_C2D_OfflineSingleMessage_ShouldSucceed() } } - [Fact, TestPriority(103)] + [Fact] + [TestPriority(103)] public async void Receive_C2D_SingleMessage_AfterOfflineMessage_ShouldSucceed() { // Arrange @@ -122,7 +125,7 @@ public async void Receive_C2D_SingleMessage_AfterOfflineMessage_ShouldSucceed() await Task.Delay(TimeSpan.FromSeconds(30)); // Act - //Send message before device is listening + // Send message before device is listening Message message = this.CreateMessage(out string payload); await serviceClient.SendAsync(deviceName, message); @@ -145,7 +148,8 @@ public async void Receive_C2D_SingleMessage_AfterOfflineMessage_ShouldSucceed() } } - [Fact, TestPriority(104)] + [Fact] + [TestPriority(104)] public async void Receive_C2D_NotSubscribed_OfflineSingleMessage_ShouldThrow() { // Arrange @@ -161,7 +165,7 @@ public async void Receive_C2D_NotSubscribed_OfflineSingleMessage_ShouldThrow() await serviceClient.OpenAsync(); // Act - //Send message before device is listening + // Send message before device is listening Message message = this.CreateMessage(out string payload); await serviceClient.SendAsync(deviceName, message); @@ -236,6 +240,7 @@ async Task Cleanup(DeviceClient deviceClient, ServiceClient serviceClient, Regis { await deviceClient.CloseAsync(); } + if (serviceClient != null) { await serviceClient.CloseAsync(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/DependencyManager.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/DependencyManager.cs index b3d6f9811fa..34826d91e99 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/DependencyManager.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/DependencyManager.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using System.Security.Cryptography.X509Certificates; using Autofac; using DotNetty.Common.Internal.Logging; + using Microsoft.Azure.Devices.Client; using Microsoft.Azure.Devices.Edge.Hub.CloudProxy; using Microsoft.Azure.Devices.Edge.Hub.Core.Config; using Microsoft.Azure.Devices.Edge.Hub.Mqtt; @@ -18,6 +19,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Moq; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Service.Constants; class DependencyManager : IDependencyManager { @@ -25,6 +27,43 @@ class DependencyManager : IDependencyManager readonly X509Certificate2 serverCertificate; readonly IList trustBundle; + readonly IList inboundTemplates = new List() + { + "devices/{deviceId}/messages/events/{params}/", + "devices/{deviceId}/messages/events/", + "devices/{deviceId}/modules/{moduleId}/messages/events/{params}/", + "devices/{deviceId}/modules/{moduleId}/messages/events/", + "$iothub/methods/res/{statusCode}/?$rid={correlationId}", + "$iothub/methods/res/{statusCode}/?$rid={correlationId}&foo={bar}" + }; + + readonly IDictionary outboundTemplates = new Dictionary() + { + { "C2D", "devices/{deviceId}/messages/devicebound" }, + { "TwinEndpoint", "$iothub/twin/res/{statusCode}/?$rid={correlationId}" }, + { "TwinDesiredPropertyUpdate", "$iothub/twin/PATCH/properties/desired/?$version={version}" }, + { "ModuleEndpoint", "devices/{deviceId}/modules/{moduleId}/inputs/{inputName}" } + }; + + readonly IDictionary routes = new Dictionary() + { + ["r1"] = "FROM /messages/events INTO $upstream", + ["r2"] = "FROM /messages/modules/senderA INTO BrokeredEndpoint(\"/modules/receiverA/inputs/input1\")", + ["r3"] = "FROM /messages/modules/senderB INTO BrokeredEndpoint(\"/modules/receiverA/inputs/input1\")", + ["r4"] = "FROM /messages/modules/sender1 INTO BrokeredEndpoint(\"/modules/receiver1/inputs/input1\")", + ["r5"] = "FROM /messages/modules/sender2 INTO BrokeredEndpoint(\"/modules/receiver2/inputs/input1\")", + ["r6"] = "FROM /messages/modules/sender3 INTO BrokeredEndpoint(\"/modules/receiver3/inputs/input1\")", + ["r7"] = "FROM /messages/modules/sender4 INTO BrokeredEndpoint(\"/modules/receiver4/inputs/input1\")", + ["r8"] = "FROM /messages/modules/sender5 INTO BrokeredEndpoint(\"/modules/receiver5/inputs/input1\")", + ["r9"] = "FROM /messages/modules/sender6 INTO BrokeredEndpoint(\"/modules/receiver6/inputs/input1\")", + ["r10"] = "FROM /messages/modules/sender7 INTO BrokeredEndpoint(\"/modules/receiver7/inputs/input1\")", + ["r11"] = "FROM /messages/modules/sender8 INTO BrokeredEndpoint(\"/modules/receiver8/inputs/input1\")", + ["r12"] = "FROM /messages/modules/sender9 INTO BrokeredEndpoint(\"/modules/receiver9/inputs/input1\")", + ["r13"] = "FROM /messages/modules/sender10 INTO BrokeredEndpoint(\"/modules/receiver10/inputs/input1\")", + ["r14"] = "FROM /messages/modules/sender11/outputs/output1 INTO BrokeredEndpoint(\"/modules/receiver11/inputs/input1\")", + ["r15"] = "FROM /messages/modules/sender11/outputs/output2 INTO BrokeredEndpoint(\"/modules/receiver11/inputs/input2\")", + }; + public DependencyManager(IConfigurationRoot configuration, X509Certificate2 serverCertificate, IList trustBundle) { this.configuration = configuration; @@ -36,8 +75,8 @@ public void Register(ContainerBuilder builder) { const int ConnectionPoolSize = 10; - string edgeHubConnectionString = $"{this.configuration[Service.Constants.ConfigKey.IotHubConnectionString]};ModuleId=$edgeHub"; - Client.IotHubConnectionStringBuilder iotHubConnectionStringBuilder = Client.IotHubConnectionStringBuilder.Create(edgeHubConnectionString); + string edgeHubConnectionString = $"{this.configuration[Constants.ConfigKey.IotHubConnectionString]};ModuleId=$edgeHub"; + IotHubConnectionStringBuilder iotHubConnectionStringBuilder = IotHubConnectionStringBuilder.Create(edgeHubConnectionString); var topics = new MessageAddressConversionConfiguration(this.inboundTemplates, this.outboundTemplates); builder.RegisterModule(new LoggingModule()); @@ -99,42 +138,5 @@ public void Register(ContainerBuilder builder) builder.RegisterModule(new MqttModule(mqttSettingsConfiguration.Object, topics, this.serverCertificate, false, false, false)); builder.RegisterModule(new AmqpModule("amqps", 5671, this.serverCertificate, iotHubConnectionStringBuilder.HostName, true)); } - - readonly IList inboundTemplates = new List() - { - "devices/{deviceId}/messages/events/{params}/", - "devices/{deviceId}/messages/events/", - "devices/{deviceId}/modules/{moduleId}/messages/events/{params}/", - "devices/{deviceId}/modules/{moduleId}/messages/events/", - "$iothub/methods/res/{statusCode}/?$rid={correlationId}", - "$iothub/methods/res/{statusCode}/?$rid={correlationId}&foo={bar}" - }; - - readonly IDictionary outboundTemplates = new Dictionary() - { - { "C2D", "devices/{deviceId}/messages/devicebound" }, - { "TwinEndpoint", "$iothub/twin/res/{statusCode}/?$rid={correlationId}" }, - { "TwinDesiredPropertyUpdate", "$iothub/twin/PATCH/properties/desired/?$version={version}" }, - { "ModuleEndpoint", "devices/{deviceId}/modules/{moduleId}/inputs/{inputName}" } - }; - - readonly IDictionary routes = new Dictionary() - { - ["r1"] = "FROM /messages/events INTO $upstream", - ["r2"] = "FROM /messages/modules/senderA INTO BrokeredEndpoint(\"/modules/receiverA/inputs/input1\")", - ["r3"] = "FROM /messages/modules/senderB INTO BrokeredEndpoint(\"/modules/receiverA/inputs/input1\")", - ["r4"] = "FROM /messages/modules/sender1 INTO BrokeredEndpoint(\"/modules/receiver1/inputs/input1\")", - ["r5"] = "FROM /messages/modules/sender2 INTO BrokeredEndpoint(\"/modules/receiver2/inputs/input1\")", - ["r6"] = "FROM /messages/modules/sender3 INTO BrokeredEndpoint(\"/modules/receiver3/inputs/input1\")", - ["r7"] = "FROM /messages/modules/sender4 INTO BrokeredEndpoint(\"/modules/receiver4/inputs/input1\")", - ["r8"] = "FROM /messages/modules/sender5 INTO BrokeredEndpoint(\"/modules/receiver5/inputs/input1\")", - ["r9"] = "FROM /messages/modules/sender6 INTO BrokeredEndpoint(\"/modules/receiver6/inputs/input1\")", - ["r10"] = "FROM /messages/modules/sender7 INTO BrokeredEndpoint(\"/modules/receiver7/inputs/input1\")", - ["r11"] = "FROM /messages/modules/sender8 INTO BrokeredEndpoint(\"/modules/receiver8/inputs/input1\")", - ["r12"] = "FROM /messages/modules/sender9 INTO BrokeredEndpoint(\"/modules/receiver9/inputs/input1\")", - ["r13"] = "FROM /messages/modules/sender10 INTO BrokeredEndpoint(\"/modules/receiver10/inputs/input1\")", - ["r14"] = "FROM /messages/modules/sender11/outputs/output1 INTO BrokeredEndpoint(\"/modules/receiver11/inputs/input1\")", - ["r15"] = "FROM /messages/modules/sender11/outputs/output2 INTO BrokeredEndpoint(\"/modules/receiver11/inputs/input2\")", - }; } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeHubConnectionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeHubConnectionTest.cs index fb9f6fb6942..6918387d607 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeHubConnectionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeHubConnectionTest.cs @@ -22,6 +22,8 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using Moq; using Newtonsoft.Json; using Xunit; + using IotHubConnectionStringBuilder = Microsoft.Azure.Devices.IotHubConnectionStringBuilder; + using Message = Microsoft.Azure.Devices.Client.Message; [Integration] [Collection("Microsoft.Azure.Devices.Edge.Hub.E2E.Test")] @@ -38,13 +40,13 @@ public async Task TestEdgeHubConnection() var messageConverterProvider = new MessageConverterProvider( new Dictionary() { - { typeof(Client.Message), new DeviceClientMessageConverter() }, + { typeof(Message), new DeviceClientMessageConverter() }, { typeof(Twin), twinMessageConverter }, { typeof(TwinCollection), twinCollectionMessageConverter } }); string iotHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); - Devices.IotHubConnectionStringBuilder iotHubConnectionStringBuilder = Devices.IotHubConnectionStringBuilder.Create(iotHubConnectionString); + IotHubConnectionStringBuilder iotHubConnectionStringBuilder = IotHubConnectionStringBuilder.Create(iotHubConnectionString); RegistryManager registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString); await registryManager.OpenAsync(); @@ -64,7 +66,7 @@ public async Task TestEdgeHubConnection() 1, new ClientProvider(), Option.None(), - new ClientTokenProvider(signatureProvider, iothubHostName, edgeDeviceId, TimeSpan.FromMinutes(60)), + new ClientTokenProvider(signatureProvider, iothubHostName, edgeDeviceId, TimeSpan.FromMinutes(60)), Mock.Of(), credentialsCache, edgeHubCredentials.Identity, @@ -111,8 +113,7 @@ public async Task TestEdgeHubConnection() twinCollectionMessageConverter, twinMessageConverter, versionInfo, - new NullDeviceScopeIdentitiesCache() - ); + new NullDeviceScopeIdentitiesCache()); await Task.Delay(TimeSpan.FromMinutes(1)); // Get and Validate EdgeHubConfig @@ -166,7 +167,6 @@ public async Task TestEdgeHubConnection() Assert.Equal(versionInfo, reportedProperties.VersionInfo); // Simulate a module and a downstream device that connects to Edge Hub. - string moduleId = "module1"; string sasToken = TokenHelper.CreateSasToken($"{iothubHostName}/devices/{edgeDeviceId}/modules/{moduleId}"); string moduleConnectionstring = $"HostName={iothubHostName};DeviceId={edgeDeviceId};ModuleId={moduleId};SharedAccessSignature={sasToken}"; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeToDeviceMethodTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeToDeviceMethodTest.cs index fc4d34ad753..373c47e36b6 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeToDeviceMethodTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/EdgeToDeviceMethodTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Newtonsoft.Json; using Xunit; + using IotHubConnectionStringBuilder = Microsoft.Azure.Devices.IotHubConnectionStringBuilder; [Integration] [Collection("Microsoft.Azure.Devices.Edge.Hub.E2E.Test")] @@ -21,18 +22,21 @@ public async Task InvokeMethodOnModuleTest(ITransportSettings[] transportSetting // Arrange string deviceName = string.Format("moduleMethodTest-{0}", transportSettings.First().GetTransportType().ToString("g")); string iotHubConnectionString = await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); - Devices.IotHubConnectionStringBuilder connectionStringBuilder = Devices.IotHubConnectionStringBuilder.Create(iotHubConnectionString); + IotHubConnectionStringBuilder connectionStringBuilder = IotHubConnectionStringBuilder.Create(iotHubConnectionString); RegistryManager rm = RegistryManager.CreateFromConnectionString(iotHubConnectionString); ModuleClient receiver = null; var request = new TestMethodRequest("Prop1", 10); var response = new TestMethodResponse("RespProp1", 20); TestMethodRequest receivedRequest = null; + Task MethodHandler(MethodRequest methodRequest, object context) { receivedRequest = JsonConvert.DeserializeObject(methodRequest.DataAsJson); - return Task.FromResult(new MethodResponse( - Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response)), 200)); + return Task.FromResult( + new MethodResponse( + Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response)), + 200)); } string receiverModuleName = "method-module"; @@ -72,6 +76,7 @@ Task MethodHandler(MethodRequest methodRequest, object context) { await rm.CloseAsync(); } + if (receiver != null) { await receiver.CloseAsync(); @@ -86,6 +91,7 @@ Task MethodHandler(MethodRequest methodRequest, object context) // ignored } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(10)); } @@ -103,11 +109,14 @@ public async Task InvokeMethodOnDeviceTest(ITransportSettings[] transportSetting var request = new TestMethodRequest("Prop1", 10); var response = new TestMethodResponse("RespProp1", 20); TestMethodRequest receivedRequest = null; + Task MethodHandler(MethodRequest methodRequest, object context) { receivedRequest = JsonConvert.DeserializeObject(methodRequest.DataAsJson); - return Task.FromResult(new MethodResponse( - Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response)), 200)); + return Task.FromResult( + new MethodResponse( + Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response)), + 200)); } (string deviceId, string receiverModuleConnectionString) = await RegistryManagerHelper.CreateDevice(deviceName, iotHubConnectionString, rm); @@ -144,6 +153,7 @@ Task MethodHandler(MethodRequest methodRequest, object context) { await rm.CloseAsync(); } + if (receiver != null) { await receiver.CloseAsync(); @@ -158,6 +168,7 @@ Task MethodHandler(MethodRequest methodRequest, object context) // ignored } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(10)); } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test.csproj index 9f36aaa9adf..3dc51759bb7 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test.csproj @@ -40,4 +40,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/ProtocolHeadFixture.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/ProtocolHeadFixture.cs index 8e798289e84..5cc862694fa 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/ProtocolHeadFixture.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/ProtocolHeadFixture.cs @@ -16,16 +16,17 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using Microsoft.Azure.Devices.Edge.Hub.Service; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Extensions.Logging; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Service.Constants; public class ProtocolHeadFixture : IDisposable { - public IProtocolHead ProtocolHead { get; } - public ProtocolHeadFixture() { this.ProtocolHead = InternalProtocolHeadFixture.Instance.ProtocolHead; } + public IProtocolHead ProtocolHead { get; } + public void Dispose() { } @@ -35,11 +36,7 @@ public class InternalProtocolHeadFixture IContainer container; IProtocolHead protocolHead; - public IProtocolHead ProtocolHead => this.protocolHead; - - public static InternalProtocolHeadFixture Instance { get; } = new InternalProtocolHeadFixture(); - - private InternalProtocolHeadFixture() + InternalProtocolHeadFixture() { bool.TryParse(ConfigHelper.TestConfig["Tests_StartEdgeHubService"], out bool shouldStartEdge); if (shouldStartEdge) @@ -53,6 +50,20 @@ private InternalProtocolHeadFixture() this.protocolHead?.Dispose(); } + public static InternalProtocolHeadFixture Instance { get; } = new InternalProtocolHeadFixture(); + + public IProtocolHead ProtocolHead => this.protocolHead; + + // Device SDK caches the AmqpTransportSettings that are set the first time and ignores + // all the settings used thereafter from that process. So set up a dummy connection using the test + // AmqpTransportSettings, so that Device SDK caches it and uses it thereafter + static async Task ConnectToIotHub(string connectionString) + { + DeviceClient dc = DeviceClient.CreateFromConnectionString(connectionString, TestSettings.AmqpTransportSettings); + await dc.OpenAsync(); + await dc.CloseAsync(); + } + async Task StartProtocolHead() { string certificateValue = await SecretsHelper.GetSecret("IotHubMqttHeadCert"); @@ -66,7 +77,7 @@ async Task StartProtocolHead() // TODO - After IoTHub supports MQTT, remove this and move to using MQTT for upstream connections await ConnectToIotHub(edgeDeviceConnectionString); - ConfigHelper.TestConfig[Service.Constants.ConfigKey.IotHubConnectionString] = edgeDeviceConnectionString; + ConfigHelper.TestConfig[Constants.ConfigKey.IotHubConnectionString] = edgeDeviceConnectionString; Hosting hosting = Hosting.Initialize(ConfigHelper.TestConfig, certificate, new DependencyManager(ConfigHelper.TestConfig, certificate, trustBundle), true); this.container = hosting.Container; @@ -87,16 +98,6 @@ async Task StartProtocolHead() this.protocolHead = new EdgeHubProtocolHead(new List { mqttProtocolHead, amqpProtocolHead, httpProtocolHead }, logger); await this.protocolHead.StartAsync(); } - - // Device SDK caches the AmqpTransportSettings that are set the first time and ignores - // all the settings used thereafter from that process. So set up a dummy connection using the test - // AmqpTransportSettings, so that Device SDK caches it and uses it thereafter - static async Task ConnectToIotHub(string connectionString) - { - DeviceClient dc = DeviceClient.CreateFromConnectionString(connectionString, TestSettings.AmqpTransportSettings); - await dc.OpenAsync(); - await dc.CloseAsync(); - } } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/RegistryManagerHelper.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/RegistryManagerHelper.cs index 2b9e69bdf82..c63edfc4379 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/RegistryManagerHelper.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/RegistryManagerHelper.cs @@ -12,11 +12,11 @@ public static async Task> CreateDevice(string devicePrefix { string deviceName = devicePrefix + Guid.NewGuid(); var device = new Device(deviceName) - { + { Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas } }; - if(iotEdgeCapable) + if (iotEdgeCapable) { device.Capabilities = new DeviceCapabilities { IotEdge = true }; } @@ -42,17 +42,6 @@ public static async Task CreateModuleIfNotExists(RegistryManager registr return moduleConnectionString; } - static string GetDeviceConnectionString(Device device, string hostName, bool appendGatewayHostName = true) - { - string connectionString = $"HostName={hostName};DeviceId={device.Id};SharedAccessKey={device.Authentication.SymmetricKey.PrimaryKey}"; - if(appendGatewayHostName) - { - string gatewayHostname = ConfigHelper.TestConfig["GatewayHostname"]; - connectionString = $"{connectionString};GatewayHostName={gatewayHostname}"; - } - return connectionString; - } - public static string GetModuleConnectionString(Module module, string hostName) { string gatewayHostname = ConfigHelper.TestConfig["GatewayHostname"]; @@ -79,5 +68,17 @@ public static async Task GetOrCreateModule(RegistryManager registryManag string gatewayHostname = ConfigHelper.TestConfig["GatewayHostname"]; return $"HostName={hostName};DeviceId={module.DeviceId};ModuleId={module.Id};SharedAccessKey={module.Authentication.SymmetricKey.PrimaryKey};GatewayHostName={gatewayHostname}"; } + + static string GetDeviceConnectionString(Device device, string hostName, bool appendGatewayHostName = true) + { + string connectionString = $"HostName={hostName};DeviceId={device.Id};SharedAccessKey={device.Authentication.SymmetricKey.PrimaryKey}"; + if (appendGatewayHostName) + { + string gatewayHostname = ConfigHelper.TestConfig["GatewayHostname"]; + connectionString = $"{connectionString};GatewayHostName={gatewayHostname}"; + } + + return connectionString; + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/StressTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/StressTest.cs index c2f68f26eed..d2a104f5897 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/StressTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/StressTest.cs @@ -6,16 +6,16 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Devices.Client; - using Microsoft.Azure.Devices.Client.Transport.Mqtt; using Microsoft.Azure.Devices.Edge.Util.Test; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; - [Integration, Stress] + [Integration] + [Stress] [Collection("Microsoft.Azure.Devices.Edge.Hub.E2E.Test")] [TestCaseOrderer("Microsoft.Azure.Devices.Edge.Util.Test.PriorityOrderer", "Microsoft.Azure.Devices.Edge.Util.Test")] public class StressTest : IClassFixture - { + { [TestPriority(301)] [Theory] [MemberData(nameof(TestSettings.TransportSettings), MemberType = typeof(TestSettings))] @@ -50,15 +50,18 @@ public async Task SingleSenderSingleReceiverTest(ITransportSettings[] transportS { await rm.CloseAsync(); } + if (sender != null) { await sender.Disconnect(); } + if (receiver != null) { await receiver.Disconnect(); } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(20)); } @@ -101,19 +104,23 @@ public async Task MultipleSendersSingleReceiverTest(ITransportSettings[] transpo { await rm.CloseAsync(); } + if (sender1 != null) { await sender1.Disconnect(); } + if (sender2 != null) { await sender2.Disconnect(); } + if (receiver != null) { await receiver.Disconnect(); } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(20)); } @@ -158,16 +165,20 @@ public async Task MultipleSendersMultipleReceivers_Count_Test(ITransportSettings { await rm.CloseAsync(); } + if (senders != null) { await Task.WhenAll(senders.Select(s => s.Disconnect())); } + if (receivers != null) { await Task.WhenAll(receivers.Select(r => r.Disconnect())); } + await (rm?.CloseAsync() ?? Task.CompletedTask); } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(20)); } @@ -210,16 +221,20 @@ public async Task MultipleSendersMultipleReceivers_Duration_Test(ITransportSetti { await rm.CloseAsync(); } + if (senders != null) { await Task.WhenAll(senders.Select(s => s.Disconnect())); } + if (receivers != null) { await Task.WhenAll(receivers.Select(r => r.Disconnect())); } + await (rm?.CloseAsync() ?? Task.CompletedTask); } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(20)); } @@ -233,6 +248,7 @@ async Task> GetModules(RegistryManager rm, string hostName, str TestModule module = await this.GetModule(rm, hostName, deviceId, moduleId, isReceiver, transportSettings); modules.Add(module); } + return modules; } @@ -244,7 +260,8 @@ async Task GetModule(RegistryManager rm, string hostName, string dev { await module.SetupReceiveMessageHandler(); } + return module; - } + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TelemetryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TelemetryTest.cs index 4c0e9db7ad7..9e14094890a 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TelemetryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TelemetryTest.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.E2E.Test using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Devices.Client; - using Microsoft.Azure.Devices.Client.Exceptions; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -48,15 +47,18 @@ async Task SendTelemetryTest(ITransportSettings[] transportSettings) { await rm.CloseAsync(); } + if (sender != null) { await sender.Disconnect(); } + if (receiver != null) { await receiver.Disconnect(); } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(10)); } @@ -101,15 +103,18 @@ async Task SendTelemetryMultipleInputsTest(ITransportSettings[] transportSetting { await rm.CloseAsync(); } + if (sender != null) { await sender.Disconnect(); } + if (receiver != null) { await receiver.Disconnect(); } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(10)); } @@ -119,7 +124,7 @@ async Task SendTelemetryMultipleInputsTest(ITransportSettings[] transportSetting async Task SendLargeMessageHandleExceptionTest(ITransportSettings[] transportSettings) { TestModule sender = null; - + string edgeDeviceConnectionString = await SecretsHelper.GetSecretFromConfigKey("edgeCapableDeviceConnStrKey"); IotHubConnectionStringBuilder connectionStringBuilder = IotHubConnectionStringBuilder.Create(edgeDeviceConnectionString); RegistryManager rm = RegistryManager.CreateFromConnectionString(edgeDeviceConnectionString); @@ -147,8 +152,9 @@ async Task SendLargeMessageHandleExceptionTest(ITransportSettings[] transportSet if (rm != null) { await rm.CloseAsync(); - } + } } + // wait for the connection to be closed on the Edge side await Task.Delay(TimeSpan.FromSeconds(10)); } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestModule.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestModule.cs index 52bece4ae9f..9df217f69f4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestModule.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestModule.cs @@ -46,14 +46,6 @@ public Task SetupReceiveMessageHandler(string input) return this.moduleClient.SetInputMessageHandlerAsync(input, this.MessageHandler, this.receivedForInput[input]); } - Task MessageHandler(Message message, object userContext) - { - int messageIndex = int.Parse(message.Properties["testId"]); - var received = userContext as ISet; - received?.Add(messageIndex); - return Task.FromResult(MessageResponse.Completed); - } - public ISet GetReceivedMessageIndices() => this.receivedForInput["_"]; public ISet GetReceivedMessageIndices(string input) => this.receivedForInput[input]; @@ -68,6 +60,7 @@ public async Task SendMessagesByCountAsync(string output, int startIndex, i { throw new TimeoutException($"Attempted to send {count} messages in {timeout.TotalSeconds} seconds, but was able to send only {sentMessagesCount}"); } + return sentMessagesCount; } @@ -78,6 +71,30 @@ public Task SendMessageAsync(string output, Message message) return this.moduleClient.SendEventAsync(output, message); } + public void SetupReceiveMethodHandler(string methodName = null, MethodCallback callback = null) + { + this.receivedMethodRequests = new List(); + MethodCallback methodCallback = callback ?? this.DefaultMethodCallback; + if (string.IsNullOrWhiteSpace(methodName)) + { + this.moduleClient.SetMethodDefaultHandlerAsync(methodCallback, null); + } + else + { + this.moduleClient.SetMethodHandlerAsync(methodName, methodCallback, null); + } + } + + public Task Disconnect() => this.moduleClient.CloseAsync(); + + Task MessageHandler(Message message, object userContext) + { + int messageIndex = int.Parse(message.Properties["testId"]); + var received = userContext as ISet; + received?.Add(messageIndex); + return Task.FromResult(MessageResponse.Completed); + } + async Task SendMessagesAsync(string output, int startIndex, int count, TimeSpan duration, TimeSpan sleepTime) { var s = new Stopwatch(); @@ -93,20 +110,6 @@ async Task SendMessagesAsync(string output, int startIndex, int count, Time return i - startIndex; } - public void SetupReceiveMethodHandler(string methodName = null, MethodCallback callback = null) - { - this.receivedMethodRequests = new List(); - MethodCallback methodCallback = callback ?? this.DefaultMethodCallback; - if (string.IsNullOrWhiteSpace(methodName)) - { - this.moduleClient.SetMethodDefaultHandlerAsync(methodCallback, null); - } - else - { - this.moduleClient.SetMethodHandlerAsync(methodName, methodCallback, null); - } - } - Task DefaultMethodCallback(MethodRequest methodRequest, object context) { this.receivedMethodRequests.Add(methodRequest); @@ -123,8 +126,6 @@ Message GetMessage(string id) return message; } - public Task Disconnect() => this.moduleClient.CloseAsync(); - class Temperature { } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestSettings.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestSettings.cs index 89908a3c5ec..e5047751816 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestSettings.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TestSettings.cs @@ -11,7 +11,7 @@ public class TestSettings { public static readonly ITransportSettings[] MqttTransportSettings = { - new MqttTransportSettings(Client.TransportType.Mqtt_Tcp_Only) + new MqttTransportSettings(TransportType.Mqtt_Tcp_Only) { RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true } @@ -19,7 +19,7 @@ public class TestSettings public static readonly ITransportSettings[] AmqpTransportSettings = { - new AmqpTransportSettings(Client.TransportType.Amqp_Tcp_Only) + new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) { RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true } @@ -35,17 +35,16 @@ public class TestSettings new AmqpTransportSettings(TransportType.Amqp_WebSocket_Only) }; - public static IList TransportSettings => TransportSettingsLazy.Value; - static readonly Lazy> TransportSettingsLazy = new Lazy>(() => GetTransportSettings(), true); + public static IList TransportSettings => TransportSettingsLazy.Value; + static IList GetTransportSettings() { IList transportSettings = new List { new object[] { AmqpTransportSettings }, new object[] { MqttTransportSettings }, - }; if (bool.TryParse(ConfigHelper.TestConfig["enableWebSocketsTests"], out bool enableWebSocketsTests) && enableWebSocketsTests) diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TwinDiffE2ETest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TwinDiffE2ETest.cs index c14eec399bd..352b210071f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TwinDiffE2ETest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.E2E.Test/TwinDiffE2ETest.cs @@ -356,9 +356,11 @@ await this.RunTestCase( { throw; } + await Task.Delay(TimeSpan.FromSeconds(5)); } } + return (deviceClient, deviceName); } @@ -379,10 +381,14 @@ async Task RunTestCase(ITransportSettings[] transportSettings, Func TestTwinUpdate(DeviceClient deviceClient, string deviceName, - RegistryManager rm, Twin twinPatch) + async Task<(TwinCollection, TwinCollection)> TestTwinUpdate( + DeviceClient deviceClient, + string deviceName, + RegistryManager rm, + Twin twinPatch) { var receivedDesiredProperties = new TwinCollection(); + Task DesiredPropertiesUpdateCallback(TwinCollection desiredproperties, object usercontext) { receivedDesiredProperties = desiredproperties; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/AuthenticationMiddlewareTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/AuthenticationMiddlewareTest.cs index 317a04bc69e..ba819570fe4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/AuthenticationMiddlewareTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/AuthenticationMiddlewareTest.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Test using Microsoft.AspNetCore.Http; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; - using Microsoft.Azure.Devices.Edge.Hub.Http; using Microsoft.Azure.Devices.Edge.Hub.Http.Middleware; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Extensions.Primitives; @@ -38,7 +37,7 @@ public async Task AuthenticateRequestTest_Success() (bool success, string message) result = await authenticationMiddleware.AuthenticateRequest(httpContext); Assert.NotNull(result); Assert.True(result.success); - Assert.Equal("", result.message); + Assert.Equal(string.Empty, result.message); } [Fact] @@ -176,7 +175,7 @@ public async Task AuthenticateRequestTest_NoApiVersion_Success() (bool success, string message) result = await authenticationMiddleware.AuthenticateRequest(httpContext); Assert.NotNull(result); Assert.True(result.success); - Assert.Equal("", result.message); + Assert.Equal(string.Empty, result.message); } [Fact] @@ -269,7 +268,7 @@ public async Task AuthenticateRequestTestX509_Success() (bool success, string message) result = await authenticationMiddleware.AuthenticateRequest(httpContext); Assert.NotNull(result); Assert.True(result.success); - Assert.Equal("", result.message); + Assert.Equal(string.Empty, result.message); } [Fact] @@ -293,7 +292,7 @@ public async Task AuthenticateRequestTestX509IgnoresAuthorizationHeader_Success( (bool success, string message) result = await authenticationMiddleware.AuthenticateRequest(httpContext); Assert.NotNull(result); Assert.True(result.success); - Assert.Equal("", result.message); + Assert.Equal(string.Empty, result.message); } [Fact] @@ -316,7 +315,7 @@ public async Task AuthenticateRequestX509Test_NoApiVersion_Success() (bool success, string message) result = await authenticationMiddleware.AuthenticateRequest(httpContext); Assert.NotNull(result); Assert.True(result.success); - Assert.Equal("", result.message); + Assert.Equal(string.Empty, result.message); } [Fact] @@ -389,6 +388,8 @@ public async Task InvalidAuthenticateRequestX509Test_NoModuleId() Assert.Equal("Request header does not contain ModuleId", result.message); } - public class SomeException : Exception { } + public class SomeException : Exception + { + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/HttpsExtensionConnectionAdapterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/HttpsExtensionConnectionAdapterTest.cs index 1ebb6ff6f09..aea071282da 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/HttpsExtensionConnectionAdapterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/HttpsExtensionConnectionAdapterTest.cs @@ -2,19 +2,11 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Test { using System; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Https; - using Microsoft.Azure.Devices.Edge.Hub.Core; - using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; - using Microsoft.Azure.Devices.Edge.Hub.Http; using Microsoft.Azure.Devices.Edge.Hub.Http.Adapters; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; - using Microsoft.Extensions.Primitives; - using Microsoft.Net.Http.Headers; - using Moq; using Xunit; + using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; [Unit] public class HttpsExtensionConnectionAdapterTest diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestTest.cs index b323cd9c58a..5d8eaa261ca 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestTest.cs @@ -10,21 +10,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Test [Unit] public class MethodRequestTest { - [Theory] - [MemberData(nameof(GetMethodRequestData))] - public void DeserializationTest(string input, MethodRequest expectedMethodRequest) - { - // Act - var methodRequest = JsonConvert.DeserializeObject(input); - - // Assert - Assert.NotNull(methodRequest); - Assert.Equal(expectedMethodRequest.MethodName, methodRequest.MethodName); - Assert.Equal(expectedMethodRequest.Payload, methodRequest.Payload); - Assert.Equal(expectedMethodRequest.ConnectTimeout, methodRequest.ConnectTimeout); - Assert.Equal(expectedMethodRequest.ResponseTimeout, methodRequest.ResponseTimeout); - } - public static IEnumerable GetMethodRequestData() { yield return new object[] @@ -75,5 +60,20 @@ public static IEnumerable GetMethodRequestData() new MethodRequest("command", new JRaw("{\"prop1\":\"value1\"}"), 30, 0) }; } + + [Theory] + [MemberData(nameof(GetMethodRequestData))] + public void DeserializationTest(string input, MethodRequest expectedMethodRequest) + { + // Act + var methodRequest = JsonConvert.DeserializeObject(input); + + // Assert + Assert.NotNull(methodRequest); + Assert.Equal(expectedMethodRequest.MethodName, methodRequest.MethodName); + Assert.Equal(expectedMethodRequest.Payload, methodRequest.Payload); + Assert.Equal(expectedMethodRequest.ConnectTimeout, methodRequest.ConnectTimeout); + Assert.Equal(expectedMethodRequest.ResponseTimeout, methodRequest.ResponseTimeout); + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestValidatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestValidatorTest.cs index 34ce2062783..35725549437 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestValidatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/MethodRequestValidatorTest.cs @@ -23,7 +23,6 @@ public void ValidateRequestTest(MethodRequest request) methodRequestValidator.Validate(request); } - [Theory] [MemberData(nameof(GetInvalidData))] public void ValidateInvalidRequestTest(MethodRequest request, Type expectedExceptionType) @@ -56,7 +55,7 @@ static IEnumerable GetInvalidData() yield return new object[] { new MethodRequest(null, new JRaw("{\"prop\":\"val\"}"), 30, 30), typeof(ArgumentException) }; - yield return new object[] { new MethodRequest("", new JRaw("{\"prop\":\"val\"}"), 30, 30), typeof(ArgumentException) }; + yield return new object[] { new MethodRequest(string.Empty, new JRaw("{\"prop\":\"val\"}"), 30, 30), typeof(ArgumentException) }; yield return new object[] { new MethodRequest(new string('1', 102), new JRaw("{\"prop\":\"val\"}"), 30, 30), typeof(ArgumentException) }; @@ -72,6 +71,13 @@ static IEnumerable GetInvalidData() class TestClass { + public TestClass(string prop1, string prop2, TestClass obj) + { + this.Prop1 = prop1; + this.Prop2 = prop2; + this.NestedObj = obj; + } + [JsonProperty("prop1")] public string Prop1 { get; } @@ -80,13 +86,6 @@ class TestClass [JsonProperty("obj")] public TestClass NestedObj { get; } - - public TestClass(string prop1, string prop2, TestClass obj) - { - this.Prop1 = prop1; - this.Prop2 = prop2; - this.NestedObj = obj; - } } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/Microsoft.Azure.Devices.Edge.Hub.Http.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/Microsoft.Azure.Devices.Edge.Hub.Http.Test.csproj index 2866016811f..09d711eee31 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/Microsoft.Azure.Devices.Edge.Hub.Http.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/Microsoft.Azure.Devices.Edge.Hub.Http.Test.csproj @@ -38,4 +38,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/TwinsControllerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/TwinsControllerTest.cs index 0e7a92a2ede..53ba88057f2 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/TwinsControllerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/TwinsControllerTest.cs @@ -13,7 +13,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Http.Test using Microsoft.AspNetCore.Routing; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; - using Microsoft.Azure.Devices.Edge.Hub.Http; using Microsoft.Azure.Devices.Edge.Hub.Http.Controllers; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; @@ -80,7 +79,7 @@ public async Task TestInvokeMethodOnModule() string command = "showdown"; string payload = "{ \"prop1\" : \"value1\" }"; - var methodRequest = new MethodRequest( command, new JRaw(payload)); + var methodRequest = new MethodRequest(command, new JRaw(payload)); IActionResult actionResult = await testController.InvokeModuleMethodAsync(WebUtility.UrlEncode(toDeviceId), WebUtility.UrlEncode(toModuleId), methodRequest); Assert.NotNull(actionResult); @@ -200,24 +199,23 @@ public async Task TestInvokeMethodWithException() Assert.Equal(objectResult.StatusCode, (int)HttpStatusCode.GatewayTimeout); } - ActionExecutingContext GetActionExecutingContextMock(IIdentity identity) + [Unit] + [Theory] + [MemberData(nameof(GetRawJsonData))] + public void GetRawJsonTest(byte[] input, JRaw expectedOutput) { - var items = new Dictionary - { - { HttpConstants.IdentityKey, identity } - }; + // Act + JRaw output = TwinsController.GetRawJson(input); - var httpContext = Mock.Of(c => c.Items == items); - var actionContext = new ActionContext(httpContext, Mock.Of(), Mock.Of()); - var actionExecutingContext = new ActionExecutingContext(actionContext, Mock.Of>(), Mock.Of>(), new object()); - return actionExecutingContext; + // Assert + Assert.Equal(expectedOutput, output); } static IEnumerable GetRawJsonData() { yield return new object[] { null, null }; - yield return new object[] { new byte[0], null}; + yield return new object[] { new byte[0], null }; object obj = new { @@ -231,16 +229,17 @@ static IEnumerable GetRawJsonData() yield return new object[] { Encoding.UTF8.GetBytes(json), new JRaw(json) }; } - [Unit] - [Theory] - [MemberData(nameof(GetRawJsonData))] - public void GetRawJsonTest(byte[] input, JRaw expectedOutput) + ActionExecutingContext GetActionExecutingContextMock(IIdentity identity) { - // Act - JRaw output = TwinsController.GetRawJson(input); + var items = new Dictionary + { + { HttpConstants.IdentityKey, identity } + }; - // Assert - Assert.Equal(expectedOutput, output); + var httpContext = Mock.Of(c => c.Items == items); + var actionContext = new ActionContext(httpContext, Mock.Of(), Mock.Of()); + var actionExecutingContext = new ActionExecutingContext(actionContext, Mock.Of>(), Mock.Of>(), new object()); + return actionExecutingContext; } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketHandlingMiddlewareTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketHandlingMiddlewareTest.cs index 1dd9eb14114..28b1e446bf2 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketHandlingMiddlewareTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketHandlingMiddlewareTest.cs @@ -23,17 +23,15 @@ public class WebSocketHandlingMiddlewareTest [Fact] public void CtorThrowsWhenRequestDelegateIsNull() { - Assert.Throws(() => - new WebSocketHandlingMiddleware(null, Mock.Of()) - ); + Assert.Throws( + () => new WebSocketHandlingMiddleware(null, Mock.Of())); } [Fact] public void CtorThrowsWhenWebSocketListenerRegistryIsNull() { - Assert.Throws(() => - new WebSocketHandlingMiddleware(Mock.Of(), null) - ); + Assert.Throws( + () => new WebSocketHandlingMiddleware(Mock.Of(), null)); } [Fact] @@ -115,45 +113,70 @@ public async Task SetsBadrequestWhenNoRegisteredListener() Assert.Equal((int)HttpStatusCode.BadRequest, httpContext.Response.StatusCode); } + static IWebSocketListenerRegistry ObservingWebSocketListenerRegistry(List correlationIds) + { + var registry = new Mock(); + var listener = new Mock(); + + listener.Setup( + wsl => wsl.ProcessWebSocketRequestAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Returns(Task.CompletedTask) + .Callback, EndPoint, string>((ws, ep1, ep2, id) => correlationIds.Add(id)); + registry + .Setup(wslr => wslr.GetListener(It.IsAny>())) + .Returns(Option.Some(listener.Object)); + + return registry.Object; + } + HttpContext WebSocketRequestContext() { - return Mock.Of(ctx => - ctx.WebSockets == Mock.Of(wsm => wsm.IsWebSocketRequest == true) - && ctx.Response == Mock.Of() - && ctx.Features == Mock.Of(fc => - fc.Get() == Mock.Of( - f => f.ChainElements == new List())) - && ctx.Connection == Mock.Of(conn => conn.LocalIpAddress == new IPAddress(123) - && conn.LocalPort == It.IsAny() - && conn.RemoteIpAddress == new IPAddress(123) - && conn.RemotePort == It.IsAny() - && conn.ClientCertificate == new X509Certificate2())); + return Mock.Of( + ctx => + ctx.WebSockets == Mock.Of(wsm => wsm.IsWebSocketRequest == true) + && ctx.Response == Mock.Of() + && ctx.Features == Mock.Of( + fc => + fc.Get() == Mock.Of( + f => f.ChainElements == new List())) + && ctx.Connection == Mock.Of( + conn => conn.LocalIpAddress == new IPAddress(123) + && conn.LocalPort == It.IsAny() + && conn.RemoteIpAddress == new IPAddress(123) + && conn.RemotePort == It.IsAny() + && conn.ClientCertificate == new X509Certificate2())); } HttpContext NonWebSocketRequestContext() { - return Mock.Of(ctx => - ctx.WebSockets == Mock.Of(wsm => - wsm.IsWebSocketRequest == false)); + return Mock.Of( + ctx => + ctx.WebSockets == Mock.Of( + wsm => + wsm.IsWebSocketRequest == false)); } HttpContext ContextWithRequestedSubprotocols(params string[] subprotocols) { - return Mock.Of(ctx => - ctx.WebSockets == Mock.Of( - wsm => - wsm.WebSocketRequestedProtocols == subprotocols - && wsm.IsWebSocketRequest - && wsm.AcceptWebSocketAsync(It.IsAny()) == Task.FromResult(Mock.Of()) - ) - && ctx.Response == Mock.Of() - && ctx.Features == Mock.Of(fc => - fc.Get() == Mock.Of( - f => f.ChainElements == new List())) - && ctx.Connection == Mock.Of(conn => conn.LocalIpAddress == new IPAddress(123) - && conn.LocalPort == It.IsAny() - && conn.RemoteIpAddress == new IPAddress(123) && conn.RemotePort == It.IsAny() - && conn.ClientCertificate == new X509Certificate2())); + return Mock.Of( + ctx => + ctx.WebSockets == Mock.Of( + wsm => + wsm.WebSocketRequestedProtocols == subprotocols + && wsm.IsWebSocketRequest + && wsm.AcceptWebSocketAsync(It.IsAny()) == Task.FromResult(Mock.Of())) + && ctx.Response == Mock.Of() + && ctx.Features == Mock.Of( + fc => fc.Get() == Mock.Of(f => f.ChainElements == new List())) + && ctx.Connection == Mock.Of( + conn => conn.LocalIpAddress == new IPAddress(123) + && conn.LocalPort == It.IsAny() + && conn.RemoteIpAddress == new IPAddress(123) && conn.RemotePort == It.IsAny() + && conn.ClientCertificate == new X509Certificate2())); } RequestDelegate ThrowingNextDelegate() @@ -171,24 +194,5 @@ IWebSocketListenerRegistry ThrowingWebSocketListenerRegistry() return registry.Object; } - - static IWebSocketListenerRegistry ObservingWebSocketListenerRegistry(List correlationIds) - { - var registry = new Mock(); - var listener = new Mock(); - - listener.Setup(wsl => wsl.ProcessWebSocketRequestAsync( - It.IsAny(), - It.IsAny>(), - It.IsAny(), - It.IsAny())) - .Returns(Task.CompletedTask) - .Callback, EndPoint, string>((ws, ep1, ep2, id) => correlationIds.Add(id)); - registry - .Setup(wslr => wslr.GetListener(It.IsAny>())) - .Returns(Option.Some(listener.Object)); - - return registry.Object; - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketListenerRegistryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketListenerRegistryTest.cs index 4be1651302d..5ca6fd9cecf 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketListenerRegistryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Http.Test/WebSocketListenerRegistryTest.cs @@ -15,7 +15,7 @@ public class WebSocketListenerRegistryTest [Fact] public void CanRegisterAListener() { - IWebSocketListener listener = this._SubprotocolListener("abc"); + IWebSocketListener listener = this.SubprotocolListener("abc"); var registry = new WebSocketListenerRegistry(); Assert.True(registry.TryRegister(listener)); @@ -24,7 +24,7 @@ public void CanRegisterAListener() [Fact] public void CannotRegisterTheSameListenerTwice() { - IWebSocketListener listener = this._SubprotocolListener("abc"); + IWebSocketListener listener = this.SubprotocolListener("abc"); var registry = new WebSocketListenerRegistry(); registry.TryRegister(listener); @@ -42,7 +42,7 @@ public void CannotRegisterANullListener() [Fact] public void CannotRegisterAListenerWithoutASubProtocol() { - IWebSocketListener listener = this._SubprotocolListener(null); + IWebSocketListener listener = this.SubprotocolListener(null); var registry = new WebSocketListenerRegistry(); Assert.Throws(() => registry.TryRegister(listener)); @@ -51,7 +51,7 @@ public void CannotRegisterAListenerWithoutASubProtocol() [Fact] public void CanUnregisterAListener() { - IWebSocketListener inListener = this._SubprotocolListener("abc"); + IWebSocketListener inListener = this.SubprotocolListener("abc"); var registry = new WebSocketListenerRegistry(); registry.TryRegister(inListener); @@ -71,7 +71,7 @@ public void CannotUnregisterANonExistentListener() [Fact] public void CannotUnregisterAListenerWithANullOrWhitespaceSubProtocol() { - IWebSocketListener inListener = this._SubprotocolListener("abc"); + IWebSocketListener inListener = this.SubprotocolListener("abc"); var registry = new WebSocketListenerRegistry(); registry.TryRegister(inListener); @@ -84,8 +84,8 @@ public void CannotUnregisterAListenerWithANullOrWhitespaceSubProtocol() public void CanInvokeARegisteredListener() { var registry = new WebSocketListenerRegistry(); - registry.TryRegister(this._SubprotocolListener("abc")); - HttpContext httpContext = this._ContextWithRequestedSubprotocols("abc"); + registry.TryRegister(this.SubprotocolListener("abc")); + HttpContext httpContext = this.ContextWithRequestedSubprotocols("abc"); Option listener = registry.GetListener(httpContext.WebSockets.WebSocketRequestedProtocols); Assert.True(listener.HasValue); @@ -94,27 +94,27 @@ public void CanInvokeARegisteredListener() [Fact] public void AlwaysInvokesTheFirstMatchingListener() { - IWebSocketListener abcListener = this._SubprotocolListener("abc"); - IWebSocketListener xyzListener = this._SubprotocolListener("xyz"); + IWebSocketListener abcListener = this.SubprotocolListener("abc"); + IWebSocketListener xyzListener = this.SubprotocolListener("xyz"); var registry = new WebSocketListenerRegistry(); registry.TryRegister(abcListener); registry.TryRegister(xyzListener); - HttpContext httpContext = this._ContextWithRequestedSubprotocols("xyz", "abc"); + HttpContext httpContext = this.ContextWithRequestedSubprotocols("xyz", "abc"); var listener = registry.GetListener(httpContext.WebSockets.WebSocketRequestedProtocols); - + Assert.True(listener.HasValue); - listener.ForEach( l => Assert.Equal(l.SubProtocol, "xyz")); - //Mock.Get(xyzListener).Verify(wsl => wsl.ProcessWebSocketRequestAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + listener.ForEach(l => Assert.Equal(l.SubProtocol, "xyz")); + // Mock.Get(xyzListener).Verify(wsl => wsl.ProcessWebSocketRequestAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); } [Fact] public void CannotInvokeWhenNoListenersAreRegistered() { var registry = new WebSocketListenerRegistry(); - HttpContext httpContext = this._ContextWithRequestedSubprotocols("abc"); + HttpContext httpContext = this.ContextWithRequestedSubprotocols("abc"); Assert.False(registry.GetListener(httpContext.WebSockets.WebSocketRequestedProtocols).HasValue); } @@ -123,23 +123,25 @@ public void CannotInvokeWhenNoListenersAreRegistered() public void CannotInvokeANonExistentListener() { var registry = new WebSocketListenerRegistry(); - registry.TryRegister(this._SubprotocolListener("abc")); - HttpContext httpContext = this._ContextWithRequestedSubprotocols("xyz"); + registry.TryRegister(this.SubprotocolListener("abc")); + HttpContext httpContext = this.ContextWithRequestedSubprotocols("xyz"); Assert.False(registry.GetListener(httpContext.WebSockets.WebSocketRequestedProtocols).HasValue); } - IWebSocketListener _SubprotocolListener(string subprotocol) + IWebSocketListener SubprotocolListener(string subprotocol) { return Mock.Of(wsl => wsl.SubProtocol == subprotocol); } - HttpContext _ContextWithRequestedSubprotocols(params string[] subprotocols) + HttpContext ContextWithRequestedSubprotocols(params string[] subprotocols) { - return Mock.Of(ctx => - ctx.WebSockets == Mock.Of(wsm => - wsm.WebSocketRequestedProtocols == subprotocols) && - ctx.Response == Mock.Of()); + return Mock.Of( + ctx => + ctx.WebSockets == Mock.Of( + wsm => + wsm.WebSocketRequestedProtocols == subprotocols) && + ctx.Response == Mock.Of()); } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ByteBufferConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ByteBufferConverterTest.cs index eef063f2124..1f29f7da867 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ByteBufferConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ByteBufferConverterTest.cs @@ -9,37 +9,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test public class ByteBufferConverterTest { - static IEnumerable GetInvalidTestByteArrays() - { - yield return new object[] { null, typeof(ArgumentNullException) }; - } - - static IEnumerable GetInvalidTestByteBuffer() - { - yield return new object[] { null, typeof(ArgumentNullException) }; - } - - static IEnumerable GetTestByteArrays() - { - var rand = new Random(); - - var bytes = new byte[100]; - rand.NextBytes(bytes); - yield return new object[] { bytes }; - - bytes = new byte[200]; - rand.NextBytes(bytes); - yield return new object[] { bytes }; - - bytes = new byte[20]; - rand.NextBytes(bytes); - yield return new object[] { bytes }; - - bytes = new byte[64]; - rand.NextBytes(bytes); - yield return new object[] { bytes }; - } - [Theory] [Unit] [MemberData(nameof(GetTestByteArrays))] @@ -101,5 +70,36 @@ public void ByteBufferConversionErrorTest_Unpooled(IByteBuffer input, Type expec IByteBufferConverter converter = new ByteBufferConverter(UnpooledByteBufferAllocator.Default); Assert.Throws(expectedException, () => converter.ToByteArray(input)); } + + static IEnumerable GetInvalidTestByteArrays() + { + yield return new object[] { null, typeof(ArgumentNullException) }; + } + + static IEnumerable GetInvalidTestByteBuffer() + { + yield return new object[] { null, typeof(ArgumentNullException) }; + } + + static IEnumerable GetTestByteArrays() + { + var rand = new Random(); + + var bytes = new byte[100]; + rand.NextBytes(bytes); + yield return new object[] { bytes }; + + bytes = new byte[200]; + rand.NextBytes(bytes); + yield return new object[] { bytes }; + + bytes = new byte[20]; + rand.NextBytes(bytes); + yield return new object[] { bytes }; + + bytes = new byte[64]; + rand.NextBytes(bytes); + yield return new object[] { bytes }; + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceIdentityProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceIdentityProviderTest.cs index 4bcea293e8d..a3f1a601b3b 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceIdentityProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceIdentityProviderTest.cs @@ -14,6 +14,78 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test public class DeviceIdentityProviderTest { + [Theory] + [Integration] + [MemberData(nameof(GetIdentityProviderInputs))] + public async Task GetDeviceIdentityTest( + string iotHubHostName, + string clientId, + string username, + string password, + X509Certificate2 certificate, + IList chain, + bool authRetVal, + Type expectedType) + { + var authenticator = new Mock(); + authenticator.Setup(a => a.AuthenticateAsync(It.IsAny())).ReturnsAsync(authRetVal); + var deviceIdentityProvider = new DeviceIdentityProvider(authenticator.Object, new ClientCredentialsFactory(new IdentityProvider(iotHubHostName)), true); + if (certificate != null) + { + deviceIdentityProvider.RegisterConnectionCertificate(certificate, chain); + } + + IDeviceIdentity deviceIdentity = await deviceIdentityProvider.GetAsync(clientId, username, password, null); + Assert.IsAssignableFrom(expectedType, deviceIdentity); + } + + [Theory] + [MemberData(nameof(GetUsernames))] + [Unit] + public void ParseUsernameTest(string username, string expectedDeviceId, string expectedModuleId, string expectedDeviceClientType) + { + (string deviceId, string moduleId, string deviceClientType) = DeviceIdentityProvider.ParseUserName(username); + Assert.Equal(expectedDeviceId, deviceId); + Assert.Equal(expectedModuleId, moduleId); + Assert.Equal(expectedDeviceClientType, deviceClientType); + } + + [Theory] + [InlineData("iotHub1/device1")] + [InlineData("iotHub1/device1/fooBar")] + [InlineData("iotHub1/device1/api-version")] + [InlineData("iotHub1/device1/module1/fooBar")] + [InlineData("iotHub1/device1/module1/api-version")] + [InlineData("iotHub1/device1/module1/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client/1.5.1-preview-003/prodInfo")] + [InlineData("iotHub1/device1/module1/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client")] + [InlineData("iotHub1/device1/module1")] + [InlineData("iotHub1/device1/module1?api-version=2010-01-01?DeviceClientType=customDeviceClient1")] + [InlineData("iotHub1?api-version=2010-01-01&DeviceClientType=customDeviceClient1")] + [InlineData("iotHub1/device1/module1/?version=2010-01-01&DeviceClientType=customDeviceClient1")] + [InlineData("iotHub1//?api-version=2010-01-01&DeviceClientType=customDeviceClient1")] + [Unit] + public void ParseUserNameErrorTest(string username) + { + Assert.Throws(() => DeviceIdentityProvider.ParseUserName(username)); + } + + [Fact] + [Unit] + public async Task GetIdentityCertAuthNotEnabled() + { + string iotHubHostName = "foo.azure-devices.net"; + var authenticator = new Mock(); + authenticator.Setup(a => a.AuthenticateAsync(It.IsAny())).ReturnsAsync(true); + var deviceIdentityProvider = new DeviceIdentityProvider(authenticator.Object, new ClientCredentialsFactory(new IdentityProvider(iotHubHostName)), false); + deviceIdentityProvider.RegisterConnectionCertificate(new X509Certificate2(), new List { new X509Certificate2() }); + IDeviceIdentity deviceIdentity = await deviceIdentityProvider.GetAsync( + "Device_2", + $"127.0.0.1/Device_2/api-version=2016-11-14&DeviceClientType={Uri.EscapeDataString("Microsoft.Azure.Devices.Client/1.2.2")}", + null, + null); + Assert.Equal(UnauthenticatedDeviceIdentity.Instance, deviceIdentity); + } + static IEnumerable GetIdentityProviderInputs() { string sasToken = TokenHelper.CreateSasToken("TestHub.azure-devices.net/devices/device_2", "AAAAAAAAAAAzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"); @@ -93,97 +165,25 @@ static IEnumerable GetIdentityProviderInputs() }; } - [Theory] - [Integration] - [MemberData(nameof(GetIdentityProviderInputs))] - public async Task GetDeviceIdentityTest( - string iotHubHostName, - string clientId, - string username, - string password, - X509Certificate2 certificate, - IList chain, - bool authRetVal, - Type expectedType) - { - var authenticator = new Mock(); - authenticator.Setup(a => a.AuthenticateAsync(It.IsAny())).ReturnsAsync(authRetVal); - var deviceIdentityProvider = new DeviceIdentityProvider(authenticator.Object, new ClientCredentialsFactory(new IdentityProvider(iotHubHostName)), true); - if (certificate != null) - { - deviceIdentityProvider.RegisterConnectionCertificate(certificate, chain); - } - IDeviceIdentity deviceIdentity = await deviceIdentityProvider.GetAsync(clientId, username, password, null); - Assert.IsAssignableFrom(expectedType, deviceIdentity); - } - static IEnumerable GetUsernames() { - yield return new object[] { "iotHub1/device1/api-version=2010-01-01&DeviceClientType=customDeviceClient1", "device1", "", "customDeviceClient1" }; + yield return new object[] { "iotHub1/device1/api-version=2010-01-01&DeviceClientType=customDeviceClient1", "device1", string.Empty, "customDeviceClient1" }; yield return new object[] { "iotHub1/device1/module1/api-version=2010-01-01&DeviceClientType=customDeviceClient2", "device1", "module1", "customDeviceClient2" }; yield return new object[] { "iotHub1/device1/module1/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client/1.5.1-preview-003", "device1", "module1", "Microsoft.Azure.Devices.Client/1.5.1-preview-003" }; - yield return new object[] { "iotHub1/device1/?api-version=2010-01-01&DeviceClientType=customDeviceClient1", "device1", "", "customDeviceClient1" }; + yield return new object[] { "iotHub1/device1/?api-version=2010-01-01&DeviceClientType=customDeviceClient1", "device1", string.Empty, "customDeviceClient1" }; yield return new object[] { "iotHub1/device1/module1/?api-version=2010-01-01&DeviceClientType=customDeviceClient1", "device1", "module1", "customDeviceClient1" }; - yield return new object[] { "iotHub1/device1/api-version=2010-01-01&DeviceClientType1=customDeviceClient1", "device1", "", "" }; - - yield return new object[] { "iotHub1/device1/module1/api-version=2010-01-01&", "device1", "module1", "" }; + yield return new object[] { "iotHub1/device1/api-version=2010-01-01&DeviceClientType1=customDeviceClient1", "device1", string.Empty, string.Empty }; - yield return new object[] { "iotHub1/device1/?api-version=2010-01-01", "device1", "", "" }; + yield return new object[] { "iotHub1/device1/module1/api-version=2010-01-01&", "device1", "module1", string.Empty }; - yield return new object[] { "iotHub1/device1/module1/?api-version=2010-01-01&Foo=customDeviceClient1", "device1", "module1", "" }; - - } + yield return new object[] { "iotHub1/device1/?api-version=2010-01-01", "device1", string.Empty, string.Empty }; - [Theory] - [MemberData(nameof(GetUsernames))] - [Unit] - public void ParseUsernameTest(string username, string expectedDeviceId, string expectedModuleId, string expectedDeviceClientType) - { - (string deviceId, string moduleId, string deviceClientType) = DeviceIdentityProvider.ParseUserName(username); - Assert.Equal(expectedDeviceId, deviceId); - Assert.Equal(expectedModuleId, moduleId); - Assert.Equal(expectedDeviceClientType, deviceClientType); - } - - [Theory] - [InlineData("iotHub1/device1")] - [InlineData("iotHub1/device1/fooBar")] - [InlineData("iotHub1/device1/api-version")] - [InlineData("iotHub1/device1/module1/fooBar")] - [InlineData("iotHub1/device1/module1/api-version")] - [InlineData("iotHub1/device1/module1/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client/1.5.1-preview-003/prodInfo")] - [InlineData("iotHub1/device1/module1/api-version=2017-06-30/DeviceClientType=Microsoft.Azure.Devices.Client")] - [InlineData("iotHub1/device1/module1")] - [InlineData("iotHub1/device1/module1?api-version=2010-01-01?DeviceClientType=customDeviceClient1")] - [InlineData("iotHub1?api-version=2010-01-01&DeviceClientType=customDeviceClient1")] - [InlineData("iotHub1/device1/module1/?version=2010-01-01&DeviceClientType=customDeviceClient1")] - [InlineData("iotHub1//?api-version=2010-01-01&DeviceClientType=customDeviceClient1")] - [Unit] - public void ParseUserNameErrorTest(string username) - { - Assert.Throws(() => DeviceIdentityProvider.ParseUserName(username)); - } - - [Fact] - [Unit] - public async Task GetIdentityCertAuthNotEnabled() - { - string iotHubHostName = "foo.azure-devices.net"; - var authenticator = new Mock(); - authenticator.Setup(a => a.AuthenticateAsync(It.IsAny())).ReturnsAsync(true); - var deviceIdentityProvider = new DeviceIdentityProvider(authenticator.Object, new ClientCredentialsFactory(new IdentityProvider(iotHubHostName)), false); - deviceIdentityProvider.RegisterConnectionCertificate(new X509Certificate2(), new List { new X509Certificate2() }); - IDeviceIdentity deviceIdentity = await deviceIdentityProvider.GetAsync( - "Device_2", - $"127.0.0.1/Device_2/api-version=2016-11-14&DeviceClientType={Uri.EscapeDataString("Microsoft.Azure.Devices.Client/1.2.2")}", - null, - null); - Assert.Equal(UnauthenticatedDeviceIdentity.Instance, deviceIdentity); + yield return new object[] { "iotHub1/device1/module1/?api-version=2010-01-01&Foo=customDeviceClient1", "device1", "module1", string.Empty }; } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceProxyTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceProxyTest.cs index bd674bc1e27..84b89eccd6f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceProxyTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/DeviceProxyTest.cs @@ -10,34 +10,15 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test using Microsoft.Azure.Devices.ProtocolGateway.Messaging; using Moq; using Xunit; - using IProtocolGatewayMessage = ProtocolGateway.Messaging.IMessage; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Mqtt.Constants; + using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; + using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; [Unit] public class DeviceProxyTest { static readonly IByteBufferConverter ByteBufferConverter = new ByteBufferConverter(PooledByteBufferAllocator.Default); - class TestDesiredUpdateMessage - { - public EdgeMessage CoreMessage { get; } - - public ProtocolGatewayMessage PgMessage { get; } - - public TestDesiredUpdateMessage(string desiredJson) - { - this.CoreMessage = new EdgeMessage.Builder(Encoding.UTF8.GetBytes(desiredJson)) - .SetSystemProperties(new Dictionary() - { - [SystemProperties.OutboundUri] = Mqtt.Constants.OutboundUriTwinDesiredPropertyUpdate, - [SystemProperties.Version] = 1.ToString() - }) - .Build(); - - this.PgMessage = new ProtocolGatewayMessage.Builder(ByteBufferConverter.ToByteBuffer(Encoding.UTF8.GetBytes(desiredJson)), - "$iothub/twin/PATCH/properties/desired/?$version=1").Build(); - } - } - [Fact] public void OnDesiredPropertyUpdatesSendsAMessageToTheProtocolGateway() { @@ -45,7 +26,7 @@ public void OnDesiredPropertyUpdatesSendsAMessageToTheProtocolGateway() var message = new TestDesiredUpdateMessage(Update); var converter = new Mock>(); - converter.Setup(x => x.FromMessage(It.IsAny())) + converter.Setup(x => x.FromMessage(It.IsAny())) .Returns(() => message.PgMessage); var channel = new Mock>(); @@ -54,8 +35,31 @@ public void OnDesiredPropertyUpdatesSendsAMessageToTheProtocolGateway() var deviceProxy = new DeviceProxy(channel.Object, Mock.Of(), converter.Object, ByteBufferConverter); deviceProxy.OnDesiredPropertyUpdates(message.CoreMessage); - converter.Verify(x => x.FromMessage(It.Is(actualCore => message.CoreMessage.Equals(actualCore)))); + converter.Verify(x => x.FromMessage(It.Is(actualCore => message.CoreMessage.Equals(actualCore)))); channel.Verify(x => x.Handle(It.Is(actualPg => message.PgMessage.Equals(actualPg)))); } + + class TestDesiredUpdateMessage + { + public TestDesiredUpdateMessage(string desiredJson) + { + this.CoreMessage = new EdgeMessage.Builder(Encoding.UTF8.GetBytes(desiredJson)) + .SetSystemProperties( + new Dictionary() + { + [SystemProperties.OutboundUri] = Constants.OutboundUriTwinDesiredPropertyUpdate, + [SystemProperties.Version] = 1.ToString() + }) + .Build(); + + this.PgMessage = new ProtocolGatewayMessage.Builder( + ByteBufferConverter.ToByteBuffer(Encoding.UTF8.GetBytes(desiredJson)), + "$iothub/twin/PATCH/properties/desired/?$version=1").Build(); + } + + public EdgeMessage CoreMessage { get; } + + public ProtocolGatewayMessage PgMessage { get; } + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/IdentityTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/IdentityTest.cs index 3806af0f4a8..5403629aa7f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/IdentityTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/IdentityTest.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Moq; using Xunit; + using IDeviceIdentity = Microsoft.Azure.Devices.ProtocolGateway.Identity.IDeviceIdentity; public class IdentityTest { @@ -22,6 +23,81 @@ public class IdentityTest static readonly string ProductInfo = "don't care"; static readonly string DeviceClientType = $"DeviceClientType={ProductInfo}"; + [Theory] + [Unit] + [MemberData(nameof(GetIdentityInputs))] + public async Task GetIdentityTest( + string value, + string clientId, + string iotHubHostName, + string token, + bool success, + Type expectedCredentialsType, + Type expectedIdentityType, + AuthenticationType expected) + { + X509Certificate2 certificate = new X509Certificate2(); + IList chain = new List(); + IClientCredentials clientCredentials = await GetClientCredentials(iotHubHostName, clientId, value, token, token == null, string.Empty, certificate, chain); + Assert.NotNull(clientCredentials); + Assert.IsType(expectedCredentialsType, clientCredentials); + Assert.IsType(expectedIdentityType, clientCredentials.Identity); + Assert.Equal(iotHubHostName, ((Identity)clientCredentials.Identity).IotHubHostName); + Assert.Equal(ProductInfo, clientCredentials.ProductInfo); + Assert.Equal(expected, clientCredentials.AuthenticationType); + } + + [Theory] + [Unit] + [MemberData(nameof(GetIdentityWithProductInfoInputs))] + public async Task GetIdentityWithProductInfoTest(string productInfo, string username, string result) + { + IClientCredentials clientCredentials = await GetClientCredentials(Hostname, DeviceId, username, SasToken, false, productInfo); + Assert.NotNull(clientCredentials); + Assert.Equal(result, clientCredentials.ProductInfo); + } + + [Theory] + [Unit] + [MemberData(nameof(GetUsernameInputs))] + public async Task ProductInfoTest(string username, string clientId, string productInfo) + { + IClientCredentials clientCredentials = await GetClientCredentials(Hostname, clientId, username, SasToken); + Assert.NotNull(clientCredentials); + Assert.Equal(productInfo, clientCredentials.ProductInfo); + } + + [Theory] + [Unit] + [MemberData(nameof(GetBadUsernameInputs))] + public void NegativeUsernameTest(string username) + { + Assert.Throws(() => DeviceIdentityProvider.ParseUserName(username)); + } + + [Theory] + [Unit] + [MemberData(nameof(GetModuleIdentityInputs))] + public async Task GetModuleIdentityTest( + string value, + string iotHubHostName, + string token, + string deviceId, + string moduleId, + AuthenticationType authenticationType) + { + var certificate = new X509Certificate2(); + var chain = new List(); + IClientCredentials clientCredentials = await GetClientCredentials(iotHubHostName, $"{deviceId}/{moduleId}", value, token, token == null, string.Empty, certificate, chain); + Assert.NotNull(clientCredentials); + Assert.Equal(authenticationType, clientCredentials.AuthenticationType); + var hubModuleIdentity = clientCredentials.Identity as IModuleIdentity; + Assert.NotNull(hubModuleIdentity); + Assert.Equal(deviceId, hubModuleIdentity.DeviceId); + Assert.Equal(moduleId, hubModuleIdentity.ModuleId); + Assert.Equal($"{deviceId}/{moduleId}", hubModuleIdentity.Id); + } + static IEnumerable GetIdentityInputs() { yield return new object[] @@ -119,28 +195,32 @@ static IEnumerable GetModuleIdentityInputs() static IEnumerable GetIdentityWithProductInfoInputs() { yield return new[] - { // happy path + { + // happy path "abc", $"{Hostname}/{DeviceId}/{ApiVersion}&{DeviceClientType}", $"abc {ProductInfo}" }; yield return new[] - { // no DeviceClientType + { + // no DeviceClientType "abc", $"{Hostname}/{DeviceId}/{ApiVersion}", "abc" }; yield return new[] - { // no caller product info + { + // no caller product info string.Empty, $"{Hostname}/{DeviceId}/{ApiVersion}&{DeviceClientType}", ProductInfo }; yield return new[] - { // no DeviceClientType OR caller product info + { + // no DeviceClientType OR caller product info string.Empty, $"{Hostname}/{DeviceId}/{ApiVersion}", string.Empty @@ -236,79 +316,6 @@ static IEnumerable GetBadUsernameInputs() yield return new[] { "hostname/deviceId/moduleId/api-version=whatever/tooManySegments" }; } - [Theory] - [Unit] - [MemberData(nameof(GetIdentityInputs))] - public async Task GetIdentityTest(string value, - string clientId, - string iotHubHostName, - string token, - bool success, - Type expectedCredentialsType, - Type expectedIdentityType, - AuthenticationType expected) - { - X509Certificate2 certificate = new X509Certificate2(); - IList chain = new List(); - IClientCredentials clientCredentials = await GetClientCredentials(iotHubHostName, clientId, value, token, token == null, string.Empty, certificate, chain); - Assert.NotNull(clientCredentials); - Assert.IsType(expectedCredentialsType, clientCredentials); - Assert.IsType(expectedIdentityType, clientCredentials.Identity); - Assert.Equal(iotHubHostName, ((Identity)clientCredentials.Identity).IotHubHostName); - Assert.Equal(ProductInfo, clientCredentials.ProductInfo); - Assert.Equal(expected, clientCredentials.AuthenticationType); - } - - [Theory] - [Unit] - [MemberData(nameof(GetIdentityWithProductInfoInputs))] - public async Task GetIdentityWithProductInfoTest(string productInfo, string username, string result) - { - IClientCredentials clientCredentials = await GetClientCredentials(Hostname, DeviceId, username, SasToken, false, productInfo); - Assert.NotNull(clientCredentials); - Assert.Equal(result, clientCredentials.ProductInfo); - } - - [Theory] - [Unit] - [MemberData(nameof(GetUsernameInputs))] - public async Task ProductInfoTest(string username, string clientId, string productInfo) - { - IClientCredentials clientCredentials = await GetClientCredentials(Hostname, clientId, username, SasToken); - Assert.NotNull(clientCredentials); - Assert.Equal(productInfo, clientCredentials.ProductInfo); - } - - [Theory] - [Unit] - [MemberData(nameof(GetBadUsernameInputs))] - public void NegativeUsernameTest(string username) - { - Assert.Throws(() => DeviceIdentityProvider.ParseUserName(username)); - } - - [Theory] - [Unit] - [MemberData(nameof(GetModuleIdentityInputs))] - public async Task GetModuleIdentityTest(string value, - string iotHubHostName, - string token, - string deviceId, - string moduleId, - AuthenticationType authenticationType) - { - var certificate = new X509Certificate2(); - var chain = new List(); - IClientCredentials clientCredentials = await GetClientCredentials(iotHubHostName, $"{deviceId}/{moduleId}", value, token, token == null, string.Empty, certificate, chain); - Assert.NotNull(clientCredentials); - Assert.Equal(authenticationType, clientCredentials.AuthenticationType); - var hubModuleIdentity = clientCredentials.Identity as IModuleIdentity; - Assert.NotNull(hubModuleIdentity); - Assert.Equal(deviceId, hubModuleIdentity.DeviceId); - Assert.Equal(moduleId, hubModuleIdentity.ModuleId); - Assert.Equal($"{deviceId}/{moduleId}", hubModuleIdentity.Id); - } - static async Task GetClientCredentials(string iotHubHostName, string deviceId, string userName, string token, bool isCertAuthAllowed = false, string productInfo = "", X509Certificate2 certificate = null, IList chain = null) { var authenticator = Mock.Of(a => a.AuthenticateAsync(It.IsAny()) == Task.FromResult(true)); @@ -318,7 +325,8 @@ static async Task GetClientCredentials(string iotHubHostName { credentialIdentityProvider.RegisterConnectionCertificate(certificate, chain); } - ProtocolGateway.Identity.IDeviceIdentity deviceIdentity = await credentialIdentityProvider.GetAsync(deviceId, userName, token, null); + + IDeviceIdentity deviceIdentity = await credentialIdentityProvider.GetAsync(deviceId, userName, token, null); Assert.NotNull(deviceIdentity); IClientCredentials clientCredentials = (deviceIdentity as ProtocolGatewayIdentity)?.ClientCredentials; return clientCredentials; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessageAddressConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessageAddressConverterTest.cs index 94ade41b630..902937eb57d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessageAddressConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessageAddressConverterTest.cs @@ -11,13 +11,15 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test [Unit] public class MessageAddressConverterTest - { - static readonly string[] DontCareInput = { "" }; + { + static readonly string[] DontCareInput = { string.Empty }; static readonly IDictionary EmptyProperties = new Dictionary(); + static readonly IDictionary DontCareOutput = new Dictionary { - ["DontCare"] = "" + ["DontCare"] = string.Empty }; + static readonly IByteBuffer Payload = new ByteBufferConverter(PooledByteBufferAllocator.Default).ToByteBuffer(new byte[] { 1, 2, 3 }); [Fact] @@ -27,13 +29,6 @@ public void TestMessageAddressConverterWithEmptyConversionConfig() Assert.Throws(typeof(ArgumentException), () => new MessageAddressConverter(emptyConversionConfig)); } - static Mock CreateMessageWithSystemProps(IDictionary props) - { - var message = new Mock(); - message.SetupGet(msg => msg.SystemProperties).Returns(props); - return message; - } - [Fact] public void TryDeriveAddressWorksWithOneTemplate() { @@ -44,12 +39,12 @@ public void TryDeriveAddressWorksWithOneTemplate() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); - Mock message = CreateMessageWithSystemProps(new Dictionary() - { - ["b"] = "123" - }); + testTemplate); + Mock message = CreateMessageWithSystemProps( + new Dictionary() + { + ["b"] = "123" + }); var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("Test", message.Object, EmptyProperties, out address); @@ -73,12 +68,12 @@ public void TryDeriveAddressWorksWithMoreThanOneTemplate() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); - Mock message = CreateMessageWithSystemProps(new Dictionary() - { - ["e"] = "123" - }); + testTemplate); + Mock message = CreateMessageWithSystemProps( + new Dictionary() + { + ["e"] = "123" + }); var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("Test2", message.Object, EmptyProperties, out address); @@ -100,14 +95,14 @@ public void TryDeriveAddressWorksWithMultipleVariableTemplate() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); - Mock message = CreateMessageWithSystemProps(new Dictionary() - { - ["b"] = "123", - ["d"] = "456", - ["f"] = "789" - }); + testTemplate); + Mock message = CreateMessageWithSystemProps( + new Dictionary() + { + ["b"] = "123", + ["d"] = "456", + ["f"] = "789" + }); var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("Test", message.Object, EmptyProperties, out address); @@ -129,12 +124,12 @@ public void TryDeriveAddressFailsWithInvalidTemplate() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); - Mock message = CreateMessageWithSystemProps(new Dictionary() - { - ["b"] = "123" - }); + testTemplate); + Mock message = CreateMessageWithSystemProps( + new Dictionary() + { + ["b"] = "123" + }); var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("BadTest", message.Object, EmptyProperties, out address); @@ -153,8 +148,7 @@ public void TestTryDeriveAddressFailsWithEmptyProperties() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); + testTemplate); Mock message = CreateMessageWithSystemProps(new Dictionary()); var converter = new MessageAddressConverter(config); @@ -175,12 +169,12 @@ public void TestTryDeriveAddressFailsWithNoValidProperties() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); - Mock message = CreateMessageWithSystemProps(new Dictionary() - { - ["a"] = "123" - }); + testTemplate); + Mock message = CreateMessageWithSystemProps( + new Dictionary() + { + ["a"] = "123" + }); var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("Test", message.Object, EmptyProperties, out address); @@ -200,13 +194,13 @@ public void TestTryDeriveAddressFailsWithPartiallyMissingProperties() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); - Mock message = CreateMessageWithSystemProps(new Dictionary() - { - ["b"] = "123", - ["f"] = "789", - }); + testTemplate); + Mock message = CreateMessageWithSystemProps( + new Dictionary() + { + ["b"] = "123", + ["f"] = "789", + }); var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("Test", message.Object, EmptyProperties, out address); @@ -226,8 +220,7 @@ public void TryDeriveAddressWorksWithOneTemplateWithParameters() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); + testTemplate); var systemProperties = new Dictionary() { @@ -242,7 +235,7 @@ public void TryDeriveAddressWorksWithOneTemplateWithParameters() ["Prop2"] = "Val2", [SystemProperties.OutgoingSystemPropertiesMap[SystemProperties.ConnectionDeviceId]] = "Device1", [SystemProperties.OutgoingSystemPropertiesMap[SystemProperties.ConnectionModuleId]] = "Module1" - }; + }; Mock message = CreateMessageWithSystemProps(systemProperties); @@ -268,8 +261,7 @@ public void TryDeriveAddressWorksWithMoreThanOneTemplateWithParameters() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); + testTemplate); var systemProperties = new Dictionary() { @@ -306,8 +298,7 @@ public void TryDeriveAddressWorksWithMultipleVariableTemplateWithParameters() }; var config = new MessageAddressConversionConfiguration( DontCareInput, - testTemplate - ); + testTemplate); var systemProperties = new Dictionary() { @@ -327,7 +318,7 @@ public void TryDeriveAddressWorksWithMultipleVariableTemplateWithParameters() }; Mock message = CreateMessageWithSystemProps(systemProperties); - + var converter = new MessageAddressConverter(config); bool result = converter.TryBuildProtocolAddressFromEdgeHubMessage("Test", message.Object, properties, out address); @@ -344,8 +335,7 @@ public void TestTryParseAddressIntoMessageProperties() IList input = new List() { "a/{b}/c/{d}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/dee/"; @@ -366,8 +356,7 @@ public void TestTryParseAddressIntoMessagePropertiesMultipleInput() IList input = new List() { "a/{b}/c/{d}/", "e/{f}/g/{h}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/dee/"; @@ -389,8 +378,7 @@ public void TestTryParseAddressIntoMessagePropertiesFailsNoMatch() IList input = new List() { "a/{b}/c/{d}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/"; @@ -407,8 +395,7 @@ public void TestTryParseAddressIntoMessagePropertiesFailsNoMatchMultiple() IList input = new List() { "a/{b}/d/", "a/{b}/c/{d}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/"; @@ -425,8 +412,7 @@ public void TestTryParseAddressWithParamsIntoMessageProperties() IList input = new List() { "a/{b}/{c}/d/{params}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/cee/d/p1=v1&p2=v2&$.mid=mv1/"; @@ -448,8 +434,7 @@ public void TestTryParseAddressWithParamsIntoMessagePropertiesMultipleInput() IList input = new List() { "a/{b}/c/{params}/", "a/{b}/{c}/d/{params}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/cee/d/p1=v1&p2=v2&$.mid=mv1/"; @@ -459,7 +444,7 @@ public void TestTryParseAddressWithParamsIntoMessagePropertiesMultipleInput() Assert.True(status); Assert.Equal(5, message.Properties.Count); Assert.Equal("bee", message.Properties["b"]); - Assert.Equal("cee", message.Properties["c"]); + Assert.Equal("cee", message.Properties["c"]); Assert.Equal("v1", message.Properties["p1"]); Assert.Equal("v2", message.Properties["p2"]); Assert.Equal("mv1", message.Properties["$.mid"]); @@ -471,8 +456,7 @@ public void TestTryParseAddressWithParamsIntoMessagePropertiesFailsNoMatch() IList input = new List() { "a/{b}/c/{params}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/"; @@ -489,8 +473,7 @@ public void TestTryParseAddressWithParamsIntoMessagePropertiesFailsNoMatchMultip IList input = new List() { "a/{b}/d/", "a/{b}/c/{params}" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/p1=v1&p2=v2"; @@ -507,8 +490,7 @@ public void TestTryParseAddressWithTailingSlashOptional() IList input = new List { "a/{b}/c/{d}", "a/{b}/c/{d}/" }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/dee"; @@ -544,8 +526,7 @@ public void TestTryParseAddressWithTailingSlashOptionalWithMultipleMatches() }; var config = new MessageAddressConversionConfiguration( input, - DontCareOutput - ); + DontCareOutput); var converter = new MessageAddressConverter(config); string address = "a/bee/c/"; @@ -574,5 +555,12 @@ public void TestTryParseAddressWithTailingSlashOptionalWithMultipleMatches() Assert.Equal("bee", message2.Properties["b"]); Assert.Equal("dee", message2.Properties["d"]); } + + static Mock CreateMessageWithSystemProps(IDictionary props) + { + var message = new Mock(); + message.SetupGet(msg => msg.SystemProperties).Returns(props); + return message; + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessagingServiceClientTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessagingServiceClientTest.cs index 516b90f1a4c..72ace576ed4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessagingServiceClientTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MessagingServiceClientTest.cs @@ -15,8 +15,9 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test using Microsoft.Azure.Devices.ProtocolGateway.Messaging; using Moq; using Xunit; - using IMessage = Core.IMessage; - using IProtocolGatewayMessage = ProtocolGateway.Messaging.IMessage; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Mqtt.Constants; + using IMessage = Microsoft.Azure.Devices.Edge.Hub.Core.IMessage; + using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; [Unit] public class MessagingServiceClientTest @@ -26,59 +27,23 @@ public class MessagingServiceClientTest static readonly Mock> Channel = new Mock>(); static readonly Mock EdgeHub = new Mock(); static readonly IList Input = new List() { "devices/{deviceId}/messages/events/", "$iothub/methods/res/{statusCode}/?$rid={correlationId}" }; + static readonly IDictionary Output = new Dictionary { - [Mqtt.Constants.OutboundUriC2D] = "devices/{deviceId}/messages/devicebound", - [Mqtt.Constants.OutboundUriTwinEndpoint] = "$iothub/twin/res/{statusCode}/?$rid={correlationId}", - [Mqtt.Constants.OutboundUriModuleEndpoint] = "devices/{deviceId}/module/{moduleId}/endpoint/{endpointId}" + [Constants.OutboundUriC2D] = "devices/{deviceId}/messages/devicebound", + [Constants.OutboundUriTwinEndpoint] = "$iothub/twin/res/{statusCode}/?$rid={correlationId}", + [Constants.OutboundUriModuleEndpoint] = "devices/{deviceId}/module/{moduleId}/endpoint/{endpointId}" }; static readonly Lazy> ProtocolGatewayMessageConverter = new Lazy>(MakeProtocolGatewayMessageConverter, true); - struct Messages - { - public readonly ProtocolGatewayMessage Source; - public readonly EdgeMessage Expected; - - public Messages(string address, byte[] payload) - { - this.Source = new ProtocolGatewayMessage.Builder(ByteBufferConverter.ToByteBuffer(payload), address) - .Build(); - this.Expected = new EdgeMessage.Builder(payload).Build(); - } - } - - static Messages MakeMessages(string address = "dontcare") - { - byte[] payload = Encoding.ASCII.GetBytes("abc"); - return new Messages(address, payload); - } - - static Mock MakeDeviceListenerSpy() - { - var listener = new Mock(); - listener.Setup(x => x.ProcessDeviceMessageAsync(It.IsAny())) - .Returns(Task.CompletedTask); - listener.Setup(x => x.SendGetTwinRequest(It.IsAny())) - .Returns(Task.CompletedTask); - listener.SetupGet(x => x.Identity) - .Returns(Mock.Of()); - return listener; - } - - static ProtocolGatewayMessageConverter MakeProtocolGatewayMessageConverter() - { - var config = new MessageAddressConversionConfiguration(Input, Output); - var converter = new MessageAddressConverter(config); - return new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); - } - [Fact] public void ConstructorRequiresADeviceListener() { var converter = Mock.Of>(); - Assert.Throws(typeof(ArgumentNullException), + Assert.Throws( + typeof(ArgumentNullException), () => new MessagingServiceClient(null, converter, ByteBufferConverter)); } @@ -87,7 +52,8 @@ public void ConstructorRequiresAMessageConverter() { var listener = Mock.Of(); - Assert.Throws(typeof(ArgumentNullException), + Assert.Throws( + typeof(ArgumentNullException), () => new MessagingServiceClient(listener, null, ByteBufferConverter)); } @@ -101,7 +67,8 @@ public async Task SendAsyncThrowsIfMessageAddressIsNullOrWhiteSpace() IMessagingServiceClient client = new MessagingServiceClient(listener, converter, ByteBufferConverter); - await Assert.ThrowsAsync(typeof(ArgumentException), + await Assert.ThrowsAsync( + typeof(ArgumentException), () => client.SendAsync(message)); } @@ -110,7 +77,7 @@ public async Task SendAsyncForwardsMessagesToTheDeviceListener() { Messages m = MakeMessages("devices/d1/messages/events/"); Mock listener = MakeDeviceListenerSpy(); - m.Expected.SystemProperties[Core.SystemProperties.ConnectionDeviceId] = "d1"; + m.Expected.SystemProperties[SystemProperties.ConnectionDeviceId] = "d1"; var client = new MessagingServiceClient(listener.Object, ProtocolGatewayMessageConverter.Value, ByteBufferConverter); await client.SendAsync(m.Source); @@ -141,7 +108,8 @@ public async Task SendAsyncReturnsTheRequestedTwin() { byte[] twinBytes = Encoding.UTF8.GetBytes("don't care"); var edgeHub = Mock.Of(e => e.GetTwinAsync(It.IsAny()) == Task.FromResult(new EdgeMessage.Builder(twinBytes).Build() as IMessage)); - IDeviceListener listener = new DeviceMessageHandler(Mock.Of(i => i.Id == "d1"), + IDeviceListener listener = new DeviceMessageHandler( + Mock.Of(i => i.Id == "d1"), edgeHub, Mock.Of()); var channel = new Mock>(); @@ -184,7 +152,8 @@ public async Task SendAsyncRecognizesAPatchTwinMessage() public async Task SendAsyncSendsAPatchResponseWhenGivenACorrelationId() { var edgeHub = Mock.Of(e => e.UpdateReportedPropertiesAsync(It.IsAny(), It.IsAny()) == Task.CompletedTask); - IDeviceListener listener = new DeviceMessageHandler(Mock.Of(i => i.Id == "d1"), + IDeviceListener listener = new DeviceMessageHandler( + Mock.Of(i => i.Id == "d1"), edgeHub, Mock.Of()); var channel = new Mock>(); @@ -209,7 +178,8 @@ public async Task SendAsyncSendsAPatchResponseWhenGivenACorrelationId() public async Task SendAsyncDoesNotSendAPatchResponseWithoutACorrelationId() { var edgeHub = Mock.Of(e => e.UpdateReportedPropertiesAsync(It.IsAny(), It.IsAny()) == Task.CompletedTask); - IDeviceListener listener = new DeviceMessageHandler(Mock.Of(i => i.Id == "d1"), + IDeviceListener listener = new DeviceMessageHandler( + Mock.Of(i => i.Id == "d1"), edgeHub, Mock.Of()); var channel = new Mock>(); @@ -233,7 +203,8 @@ public async Task SendAsyncThrowsIfATwinMessageHasASubresource(string address) var client = new MessagingServiceClient(listener.Object, ProtocolGatewayMessageConverter.Value, ByteBufferConverter); - await Assert.ThrowsAsync(typeof(InvalidOperationException), + await Assert.ThrowsAsync( + typeof(InvalidOperationException), () => client.SendAsync(message)); } @@ -246,7 +217,8 @@ public async Task SendAsyncThrowsIfAGetTwinMessageDoesNotHaveACorrelationId() var client = new MessagingServiceClient(listener.Object, ProtocolGatewayMessageConverter.Value, ByteBufferConverter); - await Assert.ThrowsAsync(typeof(InvalidOperationException), + await Assert.ThrowsAsync( + typeof(InvalidOperationException), () => client.SendAsync(message)); } @@ -259,7 +231,8 @@ public async Task SendAsyncThrowsIfTheTwinMessageIsInvalid() var client = new MessagingServiceClient(listener.Object, ProtocolGatewayMessageConverter.Value, ByteBufferConverter); - await Assert.ThrowsAsync(typeof(InvalidOperationException), + await Assert.ThrowsAsync( + typeof(InvalidOperationException), () => client.SendAsync(message)); } @@ -276,15 +249,6 @@ public async Task SendAsyncSendsTheRequestedMethod() listener.Verify(p => p.ProcessMethodResponseAsync(It.Is(x => x.Properties[SystemProperties.StatusCode] == "200" && x.Properties[SystemProperties.CorrelationId] == "123")), Times.Once); } - static IEnumerable GenerateInvalidMessageIdData() - { - return new object[] - { - new object[] { null }, - new object[] { "r" } - }; - } - [Theory] [Unit] [MemberData(nameof(GenerateInvalidMessageIdData))] @@ -295,7 +259,7 @@ public async Task TestCompleteAsyncDoesNothingWhenMessageIdIsInvalid(string mess var deviceListener = new Mock(MockBehavior.Strict); // Act - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); await messagingServiceClient.CompleteAsync(messageId); // Assert @@ -312,7 +276,7 @@ public async Task TestAbandonAsyncDoesNothingWhenMessageIdIsInvalid(string messa var deviceListener = new Mock(MockBehavior.Strict); // Act - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); await messagingServiceClient.AbandonAsync(messageId); // Assert @@ -327,13 +291,14 @@ public async Task TestCompleteAsyncCallsDeviceListener() string messageId = Guid.NewGuid().ToString(); IMessageConverter messageConverter = ProtocolGatewayMessageConverter.Value; var deviceListener = new Mock(MockBehavior.Strict); - deviceListener.Setup(d => d.ProcessMessageFeedbackAsync( - It.Is(s => s.Equals(messageId, StringComparison.OrdinalIgnoreCase)), - It.Is(f => f == FeedbackStatus.Complete))) + deviceListener.Setup( + d => d.ProcessMessageFeedbackAsync( + It.Is(s => s.Equals(messageId, StringComparison.OrdinalIgnoreCase)), + It.Is(f => f == FeedbackStatus.Complete))) .Returns(TaskEx.Done); // Act - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); await messagingServiceClient.CompleteAsync(messageId); // Assert @@ -348,13 +313,14 @@ public async Task TestAbandonAsyncCallsDeviceListener() string messageId = Guid.NewGuid().ToString(); IMessageConverter messageConverter = ProtocolGatewayMessageConverter.Value; var deviceListener = new Mock(MockBehavior.Strict); - deviceListener.Setup(d => d.ProcessMessageFeedbackAsync( - It.Is(s => s.Equals(messageId, StringComparison.OrdinalIgnoreCase)), - It.Is(f => f == FeedbackStatus.Abandon))) + deviceListener.Setup( + d => d.ProcessMessageFeedbackAsync( + It.Is(s => s.Equals(messageId, StringComparison.OrdinalIgnoreCase)), + It.Is(f => f == FeedbackStatus.Abandon))) .Returns(TaskEx.Done); // Act - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); await messagingServiceClient.AbandonAsync(messageId); // Assert @@ -371,16 +337,13 @@ public async Task TestReceiveMessagingChannelComplete() var cloudProxy = new Mock(); cloudProxy.Setup(d => d.SendFeedbackMessageAsync(It.IsAny(), It.IsAny())).Callback( - (mid, status) => - { - Assert.Equal(FeedbackStatus.Complete, status); - }); + (mid, status) => { Assert.Equal(FeedbackStatus.Complete, status); }); var connectionManager = new Mock(); connectionManager.Setup(c => c.GetCloudConnection(It.IsAny())) .Returns(Task.FromResult(Option.Some(cloudProxy.Object))); var deviceListner = new DeviceMessageHandler(MockIdentity, EdgeHub.Object, connectionManager.Object); - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); Channel.Setup(r => r.Handle(It.IsAny())) .Callback( @@ -391,7 +354,7 @@ public async Task TestReceiveMessagingChannelComplete() }); messagingServiceClient.BindMessagingChannel(Channel.Object); - Core.IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); + IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); await dp.SendC2DMessageAsync(message); Assert.NotNull(msg); @@ -406,15 +369,12 @@ public async Task TestReceiveMessagingChannelReject() var dp = new DeviceProxy(Channel.Object, MockIdentity, messageConverter, ByteBufferConverter); var cloudProxy = new Mock(); cloudProxy.Setup(d => d.SendFeedbackMessageAsync(It.IsAny(), It.IsAny())).Callback( - (mid, status) => - { - Assert.Equal(FeedbackStatus.Reject, status); - }); + (mid, status) => { Assert.Equal(FeedbackStatus.Reject, status); }); var connectionManager = new Mock(); connectionManager.Setup(c => c.GetCloudConnection(It.IsAny())) .Returns(Task.FromResult(Option.Some(cloudProxy.Object))); var deviceListner = new DeviceMessageHandler(MockIdentity, EdgeHub.Object, connectionManager.Object); - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); Channel.Setup(r => r.Handle(It.IsAny())) .Callback( @@ -425,7 +385,7 @@ public async Task TestReceiveMessagingChannelReject() }); messagingServiceClient.BindMessagingChannel(Channel.Object); - Core.IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); + IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); await dp.SendC2DMessageAsync(message); Assert.NotNull(msg); @@ -440,15 +400,12 @@ public async Task TestReceiveMessagingChannelAbandon() var dp = new DeviceProxy(Channel.Object, MockIdentity, messageConverter, ByteBufferConverter); var cloudProxy = new Mock(); cloudProxy.Setup(d => d.SendFeedbackMessageAsync(It.IsAny(), It.IsAny())).Callback( - (mid, status) => - { - Assert.Equal(FeedbackStatus.Abandon, status); - }); + (mid, status) => { Assert.Equal(FeedbackStatus.Abandon, status); }); var connectionManager = new Mock(); connectionManager.Setup(c => c.GetCloudConnection(It.IsAny())) .Returns(Task.FromResult(Option.Some(cloudProxy.Object))); var deviceListner = new DeviceMessageHandler(MockIdentity, EdgeHub.Object, connectionManager.Object); - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); Channel.Setup(r => r.Handle(It.IsAny())) .Callback( @@ -459,7 +416,7 @@ public async Task TestReceiveMessagingChannelAbandon() }); messagingServiceClient.BindMessagingChannel(Channel.Object); - Core.IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); + IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); await dp.SendC2DMessageAsync(message); Assert.NotNull(msg); @@ -474,15 +431,12 @@ public async Task TestReceiveMessagingChannelDispose() var dp = new DeviceProxy(Channel.Object, MockIdentity, messageConverter, ByteBufferConverter); var cloudProxy = new Mock(); cloudProxy.Setup(d => d.CloseAsync()).Callback( - () => - { - - }); + () => { }); var connectionManager = new Mock(); connectionManager.Setup(c => c.GetCloudConnection(It.IsAny())) .Returns(Task.FromResult(Option.Some(cloudProxy.Object))); var deviceListner = new DeviceMessageHandler(MockIdentity, EdgeHub.Object, connectionManager.Object); - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListner, messageConverter, ByteBufferConverter); Channel.Setup(r => r.Handle(It.IsAny())) .Callback( @@ -493,7 +447,7 @@ public async Task TestReceiveMessagingChannelDispose() }); messagingServiceClient.BindMessagingChannel(Channel.Object); - Core.IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); + IMessage message = new EdgeMessage.Builder(new byte[] { 1, 2, 3 }).Build(); await dp.SendC2DMessageAsync(message); Assert.NotNull(msg); @@ -510,7 +464,7 @@ public async Task TestMessageCleanup() payload.Setup(p => p.Release()).Returns(true); // Act - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); IProtocolGatewayMessage protocolGatewayMessage = messagingServiceClient.CreateMessage("devices/Device1/messages/events/", payload.Object); await messagingServiceClient.SendAsync(protocolGatewayMessage); @@ -530,7 +484,7 @@ public async Task TestMessageCleanupWhenException() Exception expectedException = null; // Act - var messagingServiceClient = new Mqtt.MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); + var messagingServiceClient = new MessagingServiceClient(deviceListener.Object, messageConverter, ByteBufferConverter); IProtocolGatewayMessage protocolGatewayMessage = messagingServiceClient.CreateMessage(null, payload.Object); try { @@ -545,5 +499,52 @@ public async Task TestMessageCleanupWhenException() payload.VerifyAll(); Assert.NotNull(expectedException); } + + static Messages MakeMessages(string address = "dontcare") + { + byte[] payload = Encoding.ASCII.GetBytes("abc"); + return new Messages(address, payload); + } + + static Mock MakeDeviceListenerSpy() + { + var listener = new Mock(); + listener.Setup(x => x.ProcessDeviceMessageAsync(It.IsAny())) + .Returns(Task.CompletedTask); + listener.Setup(x => x.SendGetTwinRequest(It.IsAny())) + .Returns(Task.CompletedTask); + listener.SetupGet(x => x.Identity) + .Returns(Mock.Of()); + return listener; + } + + static ProtocolGatewayMessageConverter MakeProtocolGatewayMessageConverter() + { + var config = new MessageAddressConversionConfiguration(Input, Output); + var converter = new MessageAddressConverter(config); + return new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); + } + + static IEnumerable GenerateInvalidMessageIdData() + { + return new object[] + { + new object[] { null }, + new object[] { "r" } + }; + } + + struct Messages + { + public readonly ProtocolGatewayMessage Source; + public readonly EdgeMessage Expected; + + public Messages(string address, byte[] payload) + { + this.Source = new ProtocolGatewayMessage.Builder(ByteBufferConverter.ToByteBuffer(payload), address) + .Build(); + this.Expected = new EdgeMessage.Builder(payload).Build(); + } + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test.csproj index afb3c798b93..455337e5678 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test.csproj @@ -38,4 +38,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttFeedbackMessageTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttFeedbackMessageTest.cs index 772d14a3929..9593780c1a0 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttFeedbackMessageTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttFeedbackMessageTest.cs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; - namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test { + using System.Collections.Generic; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttWebSocketListenerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttWebSocketListenerTest.cs index 5dbdde2815a..c0126d6438a 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttWebSocketListenerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/MqttWebSocketListenerTest.cs @@ -3,13 +3,17 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test { using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Transport.Channels; + using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Identity; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.ProtocolGateway; using Microsoft.Azure.Devices.ProtocolGateway.Messaging; + using Microsoft.Azure.Devices.ProtocolGateway.Mqtt; using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; using Moq; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Mqtt.Constants; [Unit] public class MqttWebSocketListenerTest @@ -18,12 +22,12 @@ public class MqttWebSocketListenerTest public void ReturnsMqttSubProtocol() { var listener = new MqttWebSocketListener( - new ProtocolGateway.Mqtt.Settings(Mock.Of()), + new Settings(Mock.Of()), id => Task.FromResult(Mock.Of()), - Mock.Of(), + Mock.Of(), Mock.Of(), () => Mock.Of(), - new DotNetty.Transport.Channels.MultithreadEventLoopGroup(), + new MultithreadEventLoopGroup(), Mock.Of(), false, 0, diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ProtocolGatewayMessageConverterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ProtocolGatewayMessageConverterTest.cs index 8e72e9714a9..620eaa0e09b 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ProtocolGatewayMessageConverterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ProtocolGatewayMessageConverterTest.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test using Microsoft.Azure.Devices.ProtocolGateway.Mqtt; using Moq; using Xunit; + using Constants = Microsoft.Azure.Devices.Edge.Hub.Mqtt.Constants; using IProtocolGatewayMessage = Microsoft.Azure.Devices.ProtocolGateway.Messaging.IMessage; [Unit] @@ -24,7 +25,7 @@ public void TestToMessage() { var outputTemplates = new Dictionary { - ["Dummy"] = "" + ["Dummy"] = string.Empty }; var inputTemplates = new List { @@ -33,16 +34,14 @@ public void TestToMessage() }; var config = new MessageAddressConversionConfiguration( inputTemplates, - outputTemplates - ); + outputTemplates); var converter = new MessageAddressConverter(config); var properties = new Dictionary(); var protocolGatewayMessage = Mock.Of( m => m.Address == @"devices/Device_6/messages/events/%24.cid=Corrid1&%24.mid=MessageId1&Foo=Bar&Prop2=Value2&Prop3=Value3/" && m.Payload.Equals(Payload) && - m.Properties == properties - ); + m.Properties == properties); var protocolGatewayMessageConverter = new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); IMessage message = protocolGatewayMessageConverter.ToMessage(protocolGatewayMessage); @@ -64,7 +63,7 @@ public void TestToMessage_Module() { var outputTemplates = new Dictionary { - ["Dummy"] = "" + ["Dummy"] = string.Empty }; var inputTemplates = new List { @@ -75,16 +74,14 @@ public void TestToMessage_Module() }; var config = new MessageAddressConversionConfiguration( inputTemplates, - outputTemplates - ); + outputTemplates); var converter = new MessageAddressConverter(config); var properties = new Dictionary(); var protocolGatewayMessage = Mock.Of( m => m.Address == @"devices/Device_6/modules/SensorModule/messages/events/%24.cid=Corrid1&%24.mid=MessageId1&Foo=Bar&Prop2=Value2&Prop3=Value3/" && m.Payload.Equals(Payload) && - m.Properties == properties - ); + m.Properties == properties); var protocolGatewayMessageConverter = new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); IMessage message = protocolGatewayMessageConverter.ToMessage(protocolGatewayMessage); @@ -107,7 +104,7 @@ public void TestToMessage_AllSystemProperties() { var outputTemplates = new Dictionary { - ["Dummy"] = "" + ["Dummy"] = string.Empty }; var inputTemplates = new List { @@ -116,8 +113,7 @@ public void TestToMessage_AllSystemProperties() }; var config = new MessageAddressConversionConfiguration( inputTemplates, - outputTemplates - ); + outputTemplates); var converter = new MessageAddressConverter(config); var properties = new Dictionary(); @@ -152,8 +148,7 @@ public void TestToMessage_AllSystemProperties() m => m.Address == address.ToString() && m.Payload.Equals(Payload) && - m.Properties == properties - ); + m.Properties == properties); var protocolGatewayMessageConverter = new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); IMessage message = protocolGatewayMessageConverter.ToMessage(protocolGatewayMessage); @@ -194,8 +189,7 @@ public void TestFromMessage_AllSystemProperties() }; var config = new MessageAddressConversionConfiguration( inputTemplates, - outputTemplates - ); + outputTemplates); var converter = new MessageAddressConverter(config); var properties = new Dictionary @@ -206,11 +200,11 @@ public void TestFromMessage_AllSystemProperties() }; var systemProperties = new Dictionary - { - [SystemProperties.OutboundUri] = Mqtt.Constants.OutboundUriModuleEndpoint, + { + [SystemProperties.OutboundUri] = Constants.OutboundUriModuleEndpoint, [SystemProperties.LockToken] = Guid.NewGuid().ToString(), [TemplateParameters.DeviceIdTemplateParam] = DeviceId, - [Mqtt.Constants.ModuleIdTemplateParameter] = ModuleId, + [Constants.ModuleIdTemplateParameter] = ModuleId, [SystemProperties.InputName] = Input, [SystemProperties.OutputName] = "output", [SystemProperties.ContentEncoding] = "utf-8", @@ -267,8 +261,7 @@ public void TestFromMessage_DuplicateSystemProperties() }; var config = new MessageAddressConversionConfiguration( inputTemplates, - outputTemplates - ); + outputTemplates); var converter = new MessageAddressConverter(config); var properties = new Dictionary @@ -282,10 +275,10 @@ public void TestFromMessage_DuplicateSystemProperties() var systemProperties = new Dictionary { - [SystemProperties.OutboundUri] = Mqtt.Constants.OutboundUriModuleEndpoint, + [SystemProperties.OutboundUri] = Constants.OutboundUriModuleEndpoint, [SystemProperties.LockToken] = Guid.NewGuid().ToString(), [TemplateParameters.DeviceIdTemplateParam] = DeviceId, - [Mqtt.Constants.ModuleIdTemplateParameter] = ModuleId, + [Constants.ModuleIdTemplateParameter] = ModuleId, [SystemProperties.InputName] = Input, [SystemProperties.OutputName] = "output", [SystemProperties.ContentEncoding] = "utf-8", @@ -308,7 +301,8 @@ public void TestFromMessage_DuplicateSystemProperties() var protocolGatewayMessageConverter = new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); IProtocolGatewayMessage pgMessage = protocolGatewayMessageConverter.FromMessage(message); Assert.NotNull(pgMessage); - Assert.Equal(@"devices/Device1/modules/Module1/inputs/input1/Foo=Bar&Prop2=Value2&Prop3=Value3&%24.cdid=fromDevice1&%24.cmid=fromModule1&%24.ce=utf-8&%24.ct=application%2Fjson&%24.schema=schema1&%24.to=foo&%24.uid=user1&%24.cid=1234&%24.mid=m1", + Assert.Equal( + @"devices/Device1/modules/Module1/inputs/input1/Foo=Bar&Prop2=Value2&Prop3=Value3&%24.cdid=fromDevice1&%24.cmid=fromModule1&%24.ce=utf-8&%24.ct=application%2Fjson&%24.schema=schema1&%24.to=foo&%24.uid=user1&%24.cid=1234&%24.mid=m1", pgMessage.Address); Assert.Equal(12, pgMessage.Properties.Count); Assert.Equal("Bar", pgMessage.Properties["Foo"]); @@ -331,7 +325,7 @@ public void TestToMessage_NoTopicMatch() { var outputTemplates = new Dictionary { - ["Dummy"] = "" + ["Dummy"] = string.Empty }; var inputTemplates = new List { @@ -340,16 +334,14 @@ public void TestToMessage_NoTopicMatch() }; var config = new MessageAddressConversionConfiguration( inputTemplates, - outputTemplates - ); + outputTemplates); var converter = new MessageAddressConverter(config); var properties = new Dictionary(); var protocolGatewayMessage = Mock.Of( m => m.Address == @"devices/Device_6/messages/eve/%24.cid=Corrid1&%24.mid=MessageId1&Foo=Bar&Prop2=Value2&Prop3=Value3/" && m.Payload.Equals(Payload) && - m.Properties == properties - ); + m.Properties == properties); var protocolGatewayMessageConverter = new ProtocolGatewayMessageConverter(converter, ByteBufferConverter); Assert.Throws(() => protocolGatewayMessageConverter.ToMessage(protocolGatewayMessage)); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ServerWebSocketChannelTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ServerWebSocketChannelTest.cs index c59cee103f7..8d9a44567a4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ServerWebSocketChannelTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/ServerWebSocketChannelTest.cs @@ -17,17 +17,15 @@ public class ServerWebSocketChannelTest [Fact] public void CtorThrowsWhenWebSocketIsNull() { - Assert.Throws(() => - new ServerWebSocketChannel(null, Mock.Of()) - ); + Assert.Throws( + () => new ServerWebSocketChannel(null, Mock.Of())); } [Fact] public void CtorThrowsWhenEndpointIsNull() { - Assert.Throws(() => - new ServerWebSocketChannel(Mock.Of(), null) - ); + Assert.Throws( + () => new ServerWebSocketChannel(Mock.Of(), null)); } [Fact] @@ -41,9 +39,8 @@ public async Task CanCloseTheChannel() await channel.CloseAsync(); Assert.False(channel.Active); - Mock.Get(webSocket).Verify(ws => - ws.CloseAsync(WebSocketCloseStatus.NormalClosure, It.IsAny(), It.IsAny()) - ); + Mock.Get(webSocket).Verify( + ws => ws.CloseAsync(WebSocketCloseStatus.NormalClosure, It.IsAny(), It.IsAny())); } [Fact] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStatePersistenceProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStatePersistenceProviderTest.cs index 8c959c3b552..93a87338af6 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStatePersistenceProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStatePersistenceProviderTest.cs @@ -1,19 +1,53 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test { + using System.Collections.Generic; + using System.Threading.Tasks; using DotNetty.Codecs.Mqtt.Packets; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; using Moq; - using System.Collections.Generic; - using System.Threading.Tasks; using Xunit; using IProtocolgatewayDeviceIdentity = Microsoft.Azure.Devices.ProtocolGateway.Identity.IDeviceIdentity; public class SessionStatePersistenceProviderTest { + public static IEnumerable GetSubscriptionTopics() + { + var theoryData = new List(); + + theoryData.Add(new object[] { SessionStatePersistenceProvider.TwinSubscriptionTopicPrefix, DeviceSubscription.DesiredPropertyUpdates }); + theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.TwinSubscriptionTopicPrefix}/somemorestuff", DeviceSubscription.DesiredPropertyUpdates }); + theoryData.Add(new object[] { $"SomeStartingStuff/{SessionStatePersistenceProvider.TwinSubscriptionTopicPrefix}", DeviceSubscription.Unknown }); + + theoryData.Add(new object[] { SessionStatePersistenceProvider.MethodSubscriptionTopicPrefix, DeviceSubscription.Methods }); + theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.MethodSubscriptionTopicPrefix}/somemorestuff", DeviceSubscription.Methods }); + theoryData.Add(new object[] { $"SomeStartingStuff/{SessionStatePersistenceProvider.MethodSubscriptionTopicPrefix}", DeviceSubscription.Unknown }); + + theoryData.Add(new object[] { SessionStatePersistenceProvider.C2DSubscriptionTopicPrefix, DeviceSubscription.C2D }); + theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.C2DSubscriptionTopicPrefix}/somemorestuff", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { $"devices/device1/{SessionStatePersistenceProvider.C2DSubscriptionTopicPrefix}", DeviceSubscription.C2D }); + + theoryData.Add(new object[] { SessionStatePersistenceProvider.TwinResponseTopicFilter, DeviceSubscription.TwinResponse }); + theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.TwinResponseTopicFilter}/somemorestuff", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { $"devices/device1/{SessionStatePersistenceProvider.TwinResponseTopicFilter}", DeviceSubscription.Unknown }); + + theoryData.Add(new object[] { "devices/device1/modules/module1/#", DeviceSubscription.ModuleMessages }); + theoryData.Add(new object[] { "devices/device1/modules/module1/", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices/device1/modules//#", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices/device1/modules/#", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices//modules/module1/#", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices/modules/module1/#", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices/device1/module1/#", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices/device1/modules/modules/#", DeviceSubscription.ModuleMessages }); + theoryData.Add(new object[] { "/devices/device1/modules/module1/#", DeviceSubscription.Unknown }); + theoryData.Add(new object[] { "devices/device1/modules/module1", DeviceSubscription.Unknown }); + + return theoryData; + } + [Fact] [Unit] public void TestCreate_ShouldReturn_Session() @@ -99,40 +133,6 @@ public void TestSetAsync_SetMultipleSubscriptions_ShouldComplete() edgeHub.Verify(x => x.AddSubscription("d1", DeviceSubscription.Methods), Times.Once); } - public static IEnumerable GetSubscriptionTopics() - { - var theoryData = new List(); - - theoryData.Add(new object[] { SessionStatePersistenceProvider.TwinSubscriptionTopicPrefix, DeviceSubscription.DesiredPropertyUpdates }); - theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.TwinSubscriptionTopicPrefix}/somemorestuff", DeviceSubscription.DesiredPropertyUpdates }); - theoryData.Add(new object[] { $"SomeStartingStuff/{SessionStatePersistenceProvider.TwinSubscriptionTopicPrefix}", DeviceSubscription.Unknown }); - - theoryData.Add(new object[] { SessionStatePersistenceProvider.MethodSubscriptionTopicPrefix, DeviceSubscription.Methods }); - theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.MethodSubscriptionTopicPrefix}/somemorestuff", DeviceSubscription.Methods }); - theoryData.Add(new object[] { $"SomeStartingStuff/{SessionStatePersistenceProvider.MethodSubscriptionTopicPrefix}", DeviceSubscription.Unknown }); - - theoryData.Add(new object[] { SessionStatePersistenceProvider.C2DSubscriptionTopicPrefix, DeviceSubscription.C2D }); - theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.C2DSubscriptionTopicPrefix}/somemorestuff", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { $"devices/device1/{SessionStatePersistenceProvider.C2DSubscriptionTopicPrefix}", DeviceSubscription.C2D }); - - theoryData.Add(new object[] { SessionStatePersistenceProvider.TwinResponseTopicFilter, DeviceSubscription.TwinResponse }); - theoryData.Add(new object[] { $"{SessionStatePersistenceProvider.TwinResponseTopicFilter}/somemorestuff", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { $"devices/device1/{SessionStatePersistenceProvider.TwinResponseTopicFilter}", DeviceSubscription.Unknown }); - - theoryData.Add(new object[] { "devices/device1/modules/module1/#", DeviceSubscription.ModuleMessages }); - theoryData.Add(new object[] { "devices/device1/modules/module1/", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices/device1/modules//#", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices/device1/modules/#", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices//modules/module1/#", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices/modules/module1/#", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices/device1/module1/#", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices/device1/modules/modules/#", DeviceSubscription.ModuleMessages }); - theoryData.Add(new object[] { "/devices/device1/modules/module1/#", DeviceSubscription.Unknown }); - theoryData.Add(new object[] { "devices/device1/modules/module1", DeviceSubscription.Unknown }); - - return theoryData; - } - [Theory] [Unit] [MemberData(nameof(GetSubscriptionTopics))] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateStoragePersistenceProviderTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateStoragePersistenceProviderTest.cs index 9fefcfa3d7e..a6a78cc302c 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateStoragePersistenceProviderTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateStoragePersistenceProviderTest.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test { + using System; + using System.Linq; + using System.Threading.Tasks; using DotNetty.Codecs.Mqtt.Packets; using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Hub.Core.Device; @@ -8,9 +11,6 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.ProtocolGateway.Mqtt.Persistence; using Moq; - using System; - using System.Linq; - using System.Threading.Tasks; using Xunit; using IProtocolgatewayDeviceIdentity = Microsoft.Azure.Devices.ProtocolGateway.Identity.IDeviceIdentity; @@ -18,12 +18,12 @@ public class SessionStateStoragePersistenceProviderTest { const string MethodPostTopicPrefix = "$iothub/methods/POST/"; - readonly IEntityStore entityStore = new StoreProvider(new InMemoryDbStoreProvider()).GetEntityStore(Core.Constants.SessionStorePartitionKey); + readonly IEntityStore entityStore = new StoreProvider(new InMemoryDbStoreProvider()).GetEntityStore(Constants.SessionStorePartitionKey); [Fact] [Unit] public void TestCreate_ShouldReturn_Session() - { + { var sessionProvider = new SessionStateStoragePersistenceProvider(Mock.Of(), this.entityStore); ISessionState session = sessionProvider.Create(true); @@ -178,7 +178,7 @@ public async Task TestPersistence() edgeHub.Verify(x => x.AddSubscription("d1", DeviceSubscription.Methods), Times.Once); edgeHub.Verify(x => x.AddSubscription("d1", DeviceSubscription.C2D), Times.Once); edgeHub.Verify(x => x.RemoveSubscription("d1", DeviceSubscription.DesiredPropertyUpdates), Times.Once); - + ISessionState storedSession = await sessionProvider.GetAsync(identity); Assert.NotNull(storedSession); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateTest.cs index 11c256929e0..438a1516a98 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/SessionStateTest.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test using System.Linq; using System.Threading.Tasks; using DotNetty.Codecs.Mqtt.Packets; + using Microsoft.Azure.Devices.Edge.Hub.Core; using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; @@ -90,8 +91,8 @@ public void TestCopy_ShouldReturnSessionStateCopy() Assert.Equal(copySession.Subscriptions[i].CreationTime, sessionState.Subscriptions[i].CreationTime); } - Assert.Equal(copySession.SubscriptionRegistrations.Count, sessionState.SubscriptionRegistrations.Count); - foreach(KeyValuePair subscriptionRegistration in copySession.SubscriptionRegistrations) + Assert.Equal(copySession.SubscriptionRegistrations.Count, sessionState.SubscriptionRegistrations.Count); + foreach (KeyValuePair subscriptionRegistration in copySession.SubscriptionRegistrations) { Assert.Equal(subscriptionRegistration.Value, sessionState.SubscriptionRegistrations[subscriptionRegistration.Key]); } @@ -110,7 +111,7 @@ public async Task SessionStateSeralizationTest() Assert.True(sessionState.SubscriptionRegistrations[MethodPostTopicPrefix]); Assert.False(sessionState.SubscriptionRegistrations[TwinSubscriptionTopicPrefix]); - IEntityStore entityStore = new StoreProvider(new InMemoryDbStoreProvider()).GetEntityStore(Core.Constants.SessionStorePartitionKey); + IEntityStore entityStore = new StoreProvider(new InMemoryDbStoreProvider()).GetEntityStore(Constants.SessionStorePartitionKey); string key = Guid.NewGuid().ToString(); await entityStore.Put(key, sessionState); Option retrievedSessionStateOption = await entityStore.Get(key); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/TwinAddressHelperTests.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/TwinAddressHelperTests.cs index 709e0c48c63..bb3b3ee0896 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/TwinAddressHelperTests.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Mqtt.Test/TwinAddressHelperTests.cs @@ -23,8 +23,12 @@ public class TwinAddressHelperTests [InlineData("$iothub/methods/res/200/?$rid=1&", true, TwinAddressHelper.Operation.DirectMethodResponse, "200", new[] { "$rid", "1" })] [InlineData("$iothub/methods/res/200/?$rid=1&=", true, TwinAddressHelper.Operation.DirectMethodResponse, "200", new[] { "$rid", "1", "", "" })] [InlineData("$iothub/methods/res/200/?$rid=1&=value", true, TwinAddressHelper.Operation.DirectMethodResponse, "200", new[] { "$rid", "1", "", "value" })] - public void TwinTryParseOperationTests(string input, bool expectedOutcome, TwinAddressHelper.Operation expectedOperation, - string expectedSubresource, string[] expectedProperties) + public void TwinTryParseOperationTests( + string input, + bool expectedOutcome, + TwinAddressHelper.Operation expectedOperation, + string expectedSubresource, + string[] expectedProperties) { var properties = new Dictionary(); TwinAddressHelper.Operation operation; @@ -55,6 +59,7 @@ static Dictionary ComposeMapFromPairs(T[] pairs, { expectedPropertyMap.Add(keyFunc(pairs[i]), valueFunc(pairs[i + 1])); } + return expectedPropertyMap; } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/DependencyManagerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/DependencyManagerTest.cs index b5d8b120551..3df04888575 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/DependencyManagerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/DependencyManagerTest.cs @@ -11,6 +11,27 @@ namespace Microsoft.Azure.Devices.Edge.Hub.Service.Test [Unit] public class DependencyManagerTest { + [Theory] + [MemberData(nameof(GetUpstreamProtocolData))] + public void ParseUpstreamProtocolTest(string input, Option expectedValue) + { + // Arrange + IConfigurationRoot configRoot = new ConfigurationBuilder() + .AddInMemoryCollection( + new Dictionary + { + { "UpstreamProtocol", input } + }) + .Build(); + + // Act + Option upstreamProtocol = DependencyManager.GetUpstreamProtocol(configRoot); + + // Assert + Assert.Equal(expectedValue.HasValue, upstreamProtocol.HasValue); + Assert.Equal(expectedValue.OrDefault(), upstreamProtocol.OrDefault()); + } + static IEnumerable GetUpstreamProtocolData() { yield return new object[] { "Mqtt", Option.Some(UpstreamProtocol.Mqtt) }; @@ -34,30 +55,9 @@ static IEnumerable GetUpstreamProtocolData() yield return new object[] { "amqPwS", Option.Some(UpstreamProtocol.AmqpWs) }; yield return new object[] { "amqPwSt", Option.None() }; - yield return new object[] { "", Option.None() }; + yield return new object[] { string.Empty, Option.None() }; yield return new object[] { " ", Option.None() }; yield return new object[] { "mqttwebsockets", Option.None() }; } - - [Theory] - [MemberData(nameof(GetUpstreamProtocolData))] - public void ParseUpstreamProtocolTest(string input, Option expectedValue) - { - // Arrange - IConfigurationRoot configRoot = new ConfigurationBuilder() - .AddInMemoryCollection( - new Dictionary - { - { "UpstreamProtocol", input } - }) - .Build(); - - // Act - Option upstreamProtocol = DependencyManager.GetUpstreamProtocol(configRoot); - - // Assert - Assert.Equal(expectedValue.HasValue, upstreamProtocol.HasValue); - Assert.Equal(expectedValue.OrDefault(), upstreamProtocol.OrDefault()); - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/Microsoft.Azure.Devices.Edge.Hub.Service.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/Microsoft.Azure.Devices.Edge.Hub.Service.Test.csproj index 6ec9ba3a0a5..11331e136a4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/Microsoft.Azure.Devices.Edge.Hub.Service.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Edge.Hub.Service.Test/Microsoft.Azure.Devices.Edge.Hub.Service.Test.csproj @@ -37,4 +37,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/DispatcherTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/DispatcherTest.cs index 8cbb0c5e573..f9cb2c67b54 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/DispatcherTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/DispatcherTest.cs @@ -9,29 +9,26 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; - using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Endpoints; + using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Util; - using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Moq; using Xunit; [ExcludeFromCodeCoverage] public class DispatcherTest : RoutingUnitTestBase { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); static readonly IEndpointExecutorFactory AsyncExecutorFactory = new AsyncEndpointExecutorFactory(TestConstants.DefaultConfig, TestConstants.DefaultOptions); static readonly IEndpointExecutorFactory SyncExecutorFactory = new SyncEndpointExecutorFactory(TestConstants.DefaultConfig); - static IMessage MessageWithOffset(long offset) => - new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, offset); - - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var endpoint1 = new TestEndpoint("id1"); @@ -58,13 +55,15 @@ public async Task SmokeTest() Assert.Equal(new List(), endpoint2.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestConstructor() { await Assert.ThrowsAsync(() => Dispatcher.CreateAsync(null, null, null, null)); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestSetEndpoint() { var endpoint1 = new TestEndpoint("id1"); @@ -103,7 +102,8 @@ public async Task TestSetEndpoint() Assert.Equal(new List { Message2, Message3 }, endpoint4.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestRemoveEndpoint() { var endpoint1 = new TestEndpoint("id1"); @@ -141,11 +141,12 @@ public async Task TestRemoveEndpoint() } var expected = new List { Message1, Message1, Message2, Message3, Message2, Message3 }; - Assert.Equal(new List {Message1, Message1}, endpoint1.Processed); + Assert.Equal(new List { Message1, Message1 }, endpoint1.Processed); Assert.Equal(expected, endpoint3.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestReplaceEndpoints() { var endpoint1 = new TestEndpoint("id1"); @@ -182,7 +183,8 @@ public async Task TestReplaceEndpoints() Assert.Equal(expected4, endpoint4.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFailedEndpoint() { var retryStrategy = new FixedInterval(10, TimeSpan.FromSeconds(1)); @@ -211,7 +213,8 @@ public async Task TestFailedEndpoint() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClosedEndpoint() { var factory1 = new ClosedEndpointExecutorFactory(AsyncExecutorFactory); @@ -224,7 +227,8 @@ public async Task TestClosedEndpoint() await dispatcher1.CloseAsync(CancellationToken.None); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestShow() { var endpoint = new TestEndpoint("endpoint1"); @@ -234,7 +238,8 @@ public async Task TestShow() await dispatcher1.CloseAsync(CancellationToken.None); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestOffset() { var store = new Mock(); @@ -271,5 +276,8 @@ public async Task TestOffset() Assert.Equal(Option.None(), dispatcher2.Offset); await dispatcher2.CloseAsync(CancellationToken.None); } + + static IMessage MessageWithOffset(long offset) => + new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, offset); } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EndpointTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EndpointTest.cs index a5f671b22bd..7cdc120bb58 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EndpointTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EndpointTest.cs @@ -4,8 +4,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Xunit; [ExcludeFromCodeCoverage] @@ -15,7 +15,8 @@ public class EndpointTest : RoutingUnitTestBase static readonly Endpoint Endpoint2 = new TestEndpoint("id2"); static readonly Endpoint Endpoint3 = new TestEndpoint("id1"); - [Fact, Unit] + [Fact] + [Unit] public void TestEquality() { Assert.NotEqual(new TestEndpoint("id1"), new TestEndpoint("id2")); @@ -34,7 +35,8 @@ public void TestEquality() Assert.False(Endpoint1.Equals(new object())); } - [Fact, Unit] + [Fact] + [Unit] public void TestHashCode() { ISet endpoints = new HashSet @@ -48,10 +50,11 @@ public void TestHashCode() Assert.Contains(Endpoint2, endpoints); } - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { Assert.Throws(typeof(ArgumentNullException), () => new TestEndpoint(null)); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EvaluatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EvaluatorTest.cs index 26608ed00dc..a524fee9b87 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EvaluatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/EvaluatorTest.cs @@ -5,27 +5,28 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; - using Microsoft.Azure.Devices.Routing.Core.Endpoints; - using Microsoft.Azure.Devices.Routing.Core.Util; - using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; [ExcludeFromCodeCoverage] public class EvaluatorTest : RoutingUnitTestBase { static readonly IMessageSource InvalidMessageSource = CustomMessageSource.Create("/invalid/message/path"); - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage InvalidMessage = new Message(InvalidMessageSource, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value3"}, {"key2", "value2"} }); - static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value4"}, {"key2", "value2"} }); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage InvalidMessage = new Message(InvalidMessageSource, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value3" }, { "key2", "value2" } }); + static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value4" }, { "key2", "value2" } }); - [Fact, Unit] + [Fact] + [Unit] public void SmokeTest() { var endpoint1 = new TestEndpoint("id1"); var endpoint2 = new TestEndpoint("id2"); - var allEndpoints = new HashSet { endpoint1, endpoint2 }; + var allEndpoints = new HashSet { endpoint1, endpoint2 }; var route1 = new Route("id1", "false", "hub", TelemetryMessageSource.Instance, new HashSet { endpoint1 }); var route2 = new Route("id2", "true", "hub", TelemetryMessageSource.Instance, new HashSet { endpoint2 }); var routes = new HashSet { route2, route1 }; @@ -36,7 +37,8 @@ public void SmokeTest() Assert.Contains(endpoint2, endpoints); } - [Fact, Unit] + [Fact] + [Unit] public void TestSetRoute() { var endpoint1 = new TestEndpoint("id1"); @@ -73,7 +75,8 @@ public void TestSetRoute() Assert.True(evaluator.Routes.SetEquals(new HashSet { route3, route4, route1 })); } - [Fact, Unit] + [Fact] + [Unit] public void TestRemoveRoute() { var endpoint1 = new TestEndpoint("id1"); @@ -97,7 +100,7 @@ public void TestRemoveRoute() endpoints = evaluator.Evaluate(Message1); Assert.Equal(1, endpoints.Count); Assert.Contains(endpoint1, endpoints); - Assert.True(evaluator.Routes.SetEquals(new HashSet {route3, route1 })); + Assert.True(evaluator.Routes.SetEquals(new HashSet { route3, route1 })); // Remove route3 evaluator.RemoveRoute("id3"); @@ -114,7 +117,8 @@ public void TestRemoveRoute() Assert.True(evaluator.Routes.SetEquals(new HashSet { route1 })); } - [Fact, Unit] + [Fact] + [Unit] public void TestReplaceRoutes() { var endpoint1 = new TestEndpoint("id1"); @@ -139,7 +143,8 @@ public void TestReplaceRoutes() Assert.True(evaluator.Routes.SetEquals(newRoutes)); } - [Fact, Unit] + [Fact] + [Unit] public void TestMessageSource() { var endpoint1 = new NullEndpoint("id1"); @@ -161,7 +166,8 @@ public void TestMessageSource() } } - [Fact, Unit] + [Fact] + [Unit] public void TestFallback() { var endpoint1 = new NullEndpoint("id1"); @@ -185,7 +191,8 @@ public void TestFallback() Assert.Equal(0, result3.Count); } - [Fact, Unit] + [Fact] + [Unit] public void TestNoFallback() { var endpoint1 = new NullEndpoint("id1"); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/ExponentialBackoffStrategy.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/ExponentialBackoffStrategy.cs index 913f502c657..4d239e47370 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/ExponentialBackoffStrategy.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/ExponentialBackoffStrategy.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -// ReSharper disable once CheckNamespace namespace Microsoft.Azure.Devices.Common.ErrorHandling { using System; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageQueryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageQueryTest.cs index b93eb4d2668..63842e4e658 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageQueryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageQueryTest.cs @@ -4,9 +4,9 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using System; using System.Collections.Generic; using System.Text; - using Microsoft.Azure.Devices.Routing.Core.Query.Types; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query.Types; using Newtonsoft.Json; using Xunit; @@ -26,7 +26,8 @@ public class MessageQueryTest : RoutingUnitTestBase } }"; - static readonly IMessage BodyQueryMessageUtf8ValidJson = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageUtf8ValidJson = new Message( + TelemetryMessageSource.Instance, Encoding.UTF8.GetBytes(MessageBody), new Dictionary(), new Dictionary @@ -35,7 +36,8 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - static readonly IMessage BodyQueryMessageUtf16ValidJson = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageUtf16ValidJson = new Message( + TelemetryMessageSource.Instance, Encoding.Unicode.GetBytes(MessageBody), new Dictionary(), new Dictionary @@ -44,7 +46,8 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - static readonly IMessage BodyQueryMessageUtf32ValidJson = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageUtf32ValidJson = new Message( + TelemetryMessageSource.Instance, Encoding.UTF32.GetBytes(MessageBody), new Dictionary(), new Dictionary @@ -53,7 +56,8 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - static readonly IMessage BodyQueryMessageUtf8InvalidJson = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageUtf8InvalidJson = new Message( + TelemetryMessageSource.Instance, Encoding.UTF8.GetBytes("Invalid Json"), new Dictionary(), new Dictionary @@ -62,7 +66,8 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - static readonly IMessage BodyQueryMessageInvalidEncoding = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageInvalidEncoding = new Message( + TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(), new Dictionary @@ -71,7 +76,8 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - static readonly IMessage BodyQueryMessageUtf8ValidJsonMissingEncodingProperty = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageUtf8ValidJsonMissingEncodingProperty = new Message( + TelemetryMessageSource.Instance, Encoding.UTF8.GetBytes(MessageBody), new Dictionary(), new Dictionary @@ -79,7 +85,8 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - static readonly IMessage BodyQueryMessageUtf8ValidJsonMissingContentTypeProperty = new Message(TelemetryMessageSource.Instance, + static readonly IMessage BodyQueryMessageUtf8ValidJsonMissingContentTypeProperty = new Message( + TelemetryMessageSource.Instance, Encoding.UTF8.GetBytes(MessageBody), new Dictionary(), new Dictionary @@ -87,28 +94,36 @@ public class MessageQueryTest : RoutingUnitTestBase { SystemProperties.ContentEncoding, "Utf-8" }, }); - [Fact, Unit] + [Fact] + [Unit] public void TestMessageQuery() { - Assert.Equal(new QueryValue(50, QueryValueType.Double), + Assert.Equal( + new QueryValue(50, QueryValueType.Double), BodyQueryMessageUtf8ValidJson.GetQueryValue("message.Weather.Temperature")); - Assert.Equal(new QueryValue(50, QueryValueType.Double), + Assert.Equal( + new QueryValue(50, QueryValueType.Double), BodyQueryMessageUtf16ValidJson.GetQueryValue("message.Weather.Temperature")); - Assert.Equal(new QueryValue(50, QueryValueType.Double), + Assert.Equal( + new QueryValue(50, QueryValueType.Double), BodyQueryMessageUtf32ValidJson.GetQueryValue("message.Weather.Temperature")); - Assert.Throws(typeof(JsonReaderException), + Assert.Throws( + typeof(JsonReaderException), () => BodyQueryMessageUtf8InvalidJson.GetQueryValue("message.Weather.Temperature")); - Assert.Throws(typeof(JsonReaderException), + Assert.Throws( + typeof(JsonReaderException), () => BodyQueryMessageInvalidEncoding.GetQueryValue("message.Weather.Temperature")); - Assert.Throws(typeof(InvalidOperationException), + Assert.Throws( + typeof(InvalidOperationException), () => BodyQueryMessageUtf8ValidJsonMissingEncodingProperty.GetQueryValue("message.Weather.Temperature")); - Assert.Throws(typeof(InvalidOperationException), + Assert.Throws( + typeof(InvalidOperationException), () => BodyQueryMessageUtf8ValidJsonMissingContentTypeProperty.GetQueryValue("message.Weather.Temperature")); } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageTest.cs index 39f1ad12236..584ef7b59bf 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/MessageTest.cs @@ -11,22 +11,24 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test [ExcludeFromCodeCoverage] public class MessageTest : RoutingUnitTestBase { - static readonly Message Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly Message Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly Message Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly Message Message4 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key", "value"}, {"key2", "value2"} }); - static readonly Message Message5 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key", "value"} }); - static readonly Message Message6 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, new Dictionary { { "sys1", "value1" } }); - static readonly Message Message7 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, new Dictionary { { "sys1", "value1" } }); - static readonly Message Message8 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, new Dictionary { { "sys1", "value2" } }); + static readonly Message Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly Message Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly Message Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly Message Message4 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key", "value" }, { "key2", "value2" } }); + static readonly Message Message5 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key", "value" } }); + static readonly Message Message6 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, new Dictionary { { "sys1", "value1" } }); + static readonly Message Message7 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, new Dictionary { { "sys1", "value1" } }); + static readonly Message Message8 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, new Dictionary { { "sys1", "value2" } }); - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { Assert.Throws(typeof(ArgumentNullException), () => new Message(TelemetryMessageSource.Instance, new byte[0], null)); } - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { Assert.Equal(Message1, Message1); @@ -50,15 +52,17 @@ public void TestEquals() Assert.False(Message1.Equals(new object())); } - [Fact, Unit] + [Fact] + [Unit] public void TestCaseSensitivity() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary(StringComparer.InvariantCulture) { {"KEY1", "value1"}, {"key2", "value2"} }); + var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(StringComparer.InvariantCulture) { { "KEY1", "value1" }, { "key2", "value2" } }); Assert.Equal("value1", message1.Properties["key1"]); Assert.Equal("value2", message1.Properties["key2"]); } - [Fact, Unit] + [Fact] + [Unit] public void TestSet() { var messages = new HashSet diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/Microsoft.Azure.Devices.Routing.Core.Test.csproj b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/Microsoft.Azure.Devices.Routing.Core.Test.csproj index 9ad4c54d34f..a906aa5e48f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/Microsoft.Azure.Devices.Routing.Core.Test.csproj +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/Microsoft.Azure.Devices.Routing.Core.Test.csproj @@ -42,4 +42,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingPerfCounterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingPerfCounterTest.cs index eb9cde1a903..fe24988596e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingPerfCounterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingPerfCounterTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test { using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Routing.Core; using Xunit; [Unit] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingUserMetricLoggerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingUserMetricLoggerTest.cs index ac85775ed47..2c74d3833f4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingUserMetricLoggerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullRoutingUserMetricLoggerTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test { using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Routing.Core; using Xunit; [Unit] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullUserAnalyticsLoggerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullUserAnalyticsLoggerTest.cs index efa1c988393..d74dc4cdb0e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullUserAnalyticsLoggerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/NullUserAnalyticsLoggerTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test { using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Routing.Core; using Xunit; [Unit] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteStoreTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteStoreTest.cs index fa4777490a1..c184220b829 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteStoreTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteStoreTest.cs @@ -5,15 +5,16 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Xunit; [ExcludeFromCodeCoverage] public class RouteStoreTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var store1 = new RouteStore(); @@ -21,14 +22,15 @@ public async Task SmokeTest() Assert.Equal(0, config.Routes.Count); Endpoint endpoint1 = new NullEndpoint("endpoint1"); Endpoint endpoint2 = new NullEndpoint("endpoint2"); - IEnumerable allEndpoints = new List { endpoint1, endpoint2 }; + IEnumerable allEndpoints = new List { endpoint1, endpoint2 }; var route1 = new Route("id1", "true", "hub", TelemetryMessageSource.Instance, new HashSet { endpoint1 }); var route2 = new Route("id2", "false", "hub", TelemetryMessageSource.Instance, new HashSet { endpoint2 }); IEnumerable allRoutes = new List { route1, route2 }; - var store2 = new RouteStore(new Dictionary - { - { "hub", new RouterConfig(allEndpoints, allRoutes) } - }); + var store2 = new RouteStore( + new Dictionary + { + { "hub", new RouterConfig(allEndpoints, allRoutes) } + }); RouterConfig config2 = await store2.GetRouterConfigAsync("hub", CancellationToken.None); Assert.True(config2.Routes.SetEquals(new HashSet { route1, route2 })); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteTest.cs index 8d956228333..1160a9ee43e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouteTest.cs @@ -4,16 +4,16 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Query; - using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; + using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Xunit; [ExcludeFromCodeCoverage] public class RouteTest : RoutingUnitTestBase { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); static readonly Endpoint Endpoint1 = new TestEndpoint("id1"); static readonly Endpoint Endpoint2 = new TestEndpoint("id2"); @@ -27,7 +27,8 @@ public class RouteTest : RoutingUnitTestBase static readonly Route Route6 = new Route("id3", "rule3", "hub", TelemetryMessageSource.Instance, new HashSet()); static readonly Route Route7 = new Route("id2", "rule1", "hub", TelemetryMessageSource.Instance, new HashSet { Endpoint1 }); - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { Assert.Throws(typeof(ArgumentNullException), () => new Route(null, "condition", "hub", TelemetryMessageSource.Instance, new HashSet())); @@ -36,24 +37,27 @@ public void TestConstructor() Assert.Throws(typeof(ArgumentNullException), () => new Route("id", "condition", "hub", TelemetryMessageSource.Instance, null)); } - [Fact, Unit] + [Fact] + [Unit] public void SmokeTest() { - var route = new Route("id", "true", "hub", TelemetryMessageSource.Instance, new HashSet {new TestEndpoint("id1")}); + var route = new Route("id", "true", "hub", TelemetryMessageSource.Instance, new HashSet { new TestEndpoint("id1") }); Func evaluate = RouteCompiler.Instance.Compile(route); Assert.True(evaluate(Message1)); } - [Fact, Unit] + [Fact] + [Unit] public void TestShow() { - var route = new Route("id1", "select *", "hub", TelemetryMessageSource.Instance, new HashSet {new TestEndpoint("id1"), new TestEndpoint("id2")}); + var route = new Route("id1", "select *", "hub", TelemetryMessageSource.Instance, new HashSet { new TestEndpoint("id1"), new TestEndpoint("id2") }); string expected1 = "Route(\"id1\", TelemetryMessageSource, \"select *\" => (TestEndpoint(id1), TestEndpoint(id2))"; string expected2 = "Route(\"id1\", TelemetryMessageSource, \"select *\" => (TestEndpoint(id2), TestEndpoint(id1))"; Assert.True(expected1.Equals(route.ToString()) || expected2.Equals(route.ToString())); } - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { Assert.Equal(Route1, Route1); @@ -76,7 +80,8 @@ public void TestEquals() Assert.False(Route1.Equals(new object())); } - [Fact, Unit] + [Fact] + [Unit] public void TestHashCode() { ISet routes = new HashSet @@ -95,7 +100,8 @@ public void TestHashCode() Assert.Contains(Route5, routes); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("appKey", 1)] [InlineData("true", 1)] [InlineData("$body.Value = 3", 3)] @@ -107,14 +113,18 @@ public void TestHashCode() [InlineData("is_defined(x) and power(as_number(x),as_number(y))", 17)] public void TestRouteComplexity(string condition, int expected) { - var testRoute = new Route("id1", condition, "hub", TelemetryMessageSource.Instance, new HashSet - { - Endpoint1 - }); + var testRoute = new Route( + "id1", + condition, + "hub", + TelemetryMessageSource.Instance, + new HashSet + { + Endpoint1 + }); int complexity = RouteCompiler.Instance.GetComplexity(testRoute); Assert.Equal(expected, complexity); } - } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterFactoryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterFactoryTest.cs index b3ebb9d9635..2beba1c68d8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterFactoryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterFactoryTest.cs @@ -2,13 +2,14 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test { using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Xunit; public class RouterFactoryTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public async Task TestConstructor() { var factory = new RouterFactory(new AsyncEndpointExecutorFactory(TestConstants.DefaultConfig, TestConstants.DefaultOptions)); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterTest.cs index 5a43fc99586..170a85b6384 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RouterTest.cs @@ -10,26 +10,24 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Endpoints; + using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Util; - using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Moq; using Xunit; [ExcludeFromCodeCoverage] public class RouterTest : RoutingUnitTestBase { - static IMessage MessageWithOffset(long offset) => - new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, offset); - static readonly Option Fallback = Option.None(); static readonly IEndpointExecutorFactory AsyncExecutorFactory = new AsyncEndpointExecutorFactory(TestConstants.DefaultConfig, TestConstants.DefaultOptions); static readonly IEndpointExecutorFactory SyncExecutorFactory = new SyncEndpointExecutorFactory(TestConstants.DefaultConfig); - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); var endpoint1 = new TestEndpoint("id1"); var endpoint2 = new TestEndpoint("id2"); var allEndpoints = new HashSet { endpoint1, endpoint2 }; @@ -49,7 +47,8 @@ public async Task SmokeTest() Assert.Equal(expected, endpoint2.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClose() { var endpoint1 = new TestEndpoint("id1"); @@ -67,7 +66,8 @@ public async Task TestClose() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestShow() { var endpoint1 = new TestEndpoint("id1"); @@ -81,18 +81,25 @@ public async Task TestShow() await router.CloseAsync(CancellationToken.None); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestSetRoute() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, + var message1 = new Message( + TelemetryMessageSource.Instance, + new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, new Dictionary { { "systemkey1", "systemvalue1" }, { "systemkey2", "systemvalue2" } }); - var message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, + var message2 = new Message( + TelemetryMessageSource.Instance, + new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value2" }, { "key2", "value2" } }, new Dictionary { { "systemkey1", "systemvalue2" }, { "systemkey2", "systemvalue2" } }); - var message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, + var message3 = new Message( + TelemetryMessageSource.Instance, + new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value3" }, { "key2", "value2" } }, new Dictionary { { "systemkey1", "systemvalue3" }, { "systemkey2", "systemvalue2" } }); @@ -137,12 +144,13 @@ public async Task TestSetRoute() Assert.Equal(new List { message2, message2, message3 }, endpoint4.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestRemoveRoute() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - var message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value2"}, {"key2", "value2"} }); - var message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + var message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value2" }, { "key2", "value2" } }); + var message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); var endpoint1 = new TestEndpoint("id1"); var endpoint2 = new TestEndpoint("id2"); @@ -192,12 +200,13 @@ public async Task TestRemoveRoute() Assert.Equal(new List { message1, message2, message3, message1, message2, message3, message2 }, endpoint4.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestReplaceRoutes() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - var message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value2"}, {"key2", "value2"} }); - var message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + var message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value2" }, { "key2", "value2" } }); + var message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); var endpoint1 = new TestEndpoint("id1"); var endpoint2 = new TestEndpoint("id2"); @@ -249,10 +258,11 @@ public async Task TestReplaceRoutes() Assert.Equal(new List { message2, message1, message2, message3, message1, message2, message3 }, endpoint4.Processed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFailedEndpoint() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); var retryStrategy = new FixedInterval(10, TimeSpan.FromSeconds(1)); TimeSpan revivePeriod = TimeSpan.FromHours(1); TimeSpan execTimeout = TimeSpan.FromSeconds(60); @@ -266,7 +276,6 @@ public async Task TestFailedEndpoint() using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1))) using (Router router = await Router.CreateAsync("router1", "hub", new RouterConfig(endpoints, new HashSet { route }, Fallback), factory)) { - // Because the buffer size is one and we are failing we should block on the dispatch Task routing = router.RouteAsync(message1); @@ -282,7 +291,8 @@ public async Task TestFailedEndpoint() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestOffset() { var store = new Mock(); @@ -327,10 +337,11 @@ public async Task TestOffset() await router2.CloseAsync(CancellationToken.None); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFallback() { - var message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + var message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); var endpoint1 = new TestEndpoint("id1"); var endpoint2 = new TestEndpoint("id2"); var allEndpoints = new HashSet { endpoint1, endpoint2 }; @@ -348,5 +359,8 @@ public async Task TestFallback() Assert.Equal(new List(), endpoint1.Processed); Assert.Equal(expected, endpoint2.Processed); } + + static IMessage MessageWithOffset(long offset) => + new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, offset); } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RoutingTestModule.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RoutingTestModule.cs index 38c14267b2a..1c3057c859e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RoutingTestModule.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/RoutingTestModule.cs @@ -19,29 +19,32 @@ public static IContainer CreateContainer() protected override void Load(ContainerBuilder builder) { // IRoutingPerfCounter - builder.Register(c => - { - Routing.PerfCounter = NullRoutingPerfCounter.Instance; - return Routing.PerfCounter; - }) + builder.Register( + c => + { + Routing.PerfCounter = NullRoutingPerfCounter.Instance; + return Routing.PerfCounter; + }) .As() .SingleInstance(); // IRoutingUserMetricLogger - builder.Register(c => - { - Routing.UserMetricLogger = NullRoutingUserMetricLogger.Instance; - return Routing.UserMetricLogger; - }) + builder.Register( + c => + { + Routing.UserMetricLogger = NullRoutingUserMetricLogger.Instance; + return Routing.UserMetricLogger; + }) .As() .SingleInstance(); // IRoutingUserAnalyticsLogger - builder.Register(c => - { - Routing.UserAnalyticsLogger = NullUserAnalyticsLogger.Instance; - return Routing.UserAnalyticsLogger; - }) + builder.Register( + c => + { + Routing.UserAnalyticsLogger = NullUserAnalyticsLogger.Instance; + return Routing.UserAnalyticsLogger; + }) .As() .SingleInstance(); } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/SinkResultTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/SinkResultTest.cs index 8e8eeae67cb..07077ee5467 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/SinkResultTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/SinkResultTest.cs @@ -2,13 +2,14 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test { using System; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; public class SinkResultTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { var result1 = new SinkResult(new[] { 1 }, new[] { 2 }, null); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestConstants.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestConstants.cs index 99477f2393a..e82ee68eadd 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestConstants.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestConstants.cs @@ -9,18 +9,21 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test public static class TestConstants { + public const int DefaultRetryCount = 10; + public const int DefaultBatchSize = 1000; public static readonly TimeSpan DefaultRevivePeriod = TimeSpan.FromMinutes(60); public static readonly TimeSpan DefaultMinBackoff = TimeSpan.FromSeconds(2); public static readonly TimeSpan DefaultMaxBackoff = TimeSpan.FromMinutes(5); public static readonly TimeSpan DefaultDeltaBackoff = TimeSpan.FromSeconds(2); public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); public static readonly TimeSpan DefaultBatchTimeout = TimeSpan.FromSeconds(1); - public const int DefaultRetryCount = 10; public static RetryStrategy DefaultRetryStrategy { get; } = new ExponentialBackoffStrategy(DefaultRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultDeltaBackoff); + public static EndpointExecutorConfig DefaultConfig { get; } = new EndpointExecutorConfig(DefaultTimeout, DefaultRetryStrategy, DefaultRevivePeriod); + public static AsyncEndpointExecutorOptions DefaultOptions { get; } = new AsyncEndpointExecutorOptions(DefaultBatchSize, DefaultBatchTimeout); + public static ICheckpointer DefaultCheckpointer { get; } = NullCheckpointer.Instance; - public const int DefaultBatchSize = 1000; } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestNotifier.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestNotifier.cs index 587383b0fd1..35dfb5ab923 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestNotifier.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/TestNotifier.cs @@ -11,12 +11,12 @@ public class TestNotifier : INotifier Func changeCallback; Func deleteCallback; + public string IotHubName => "test"; + public Task Change(string hubName) => this.changeCallback?.Invoke(hubName) ?? TaskEx.Done; public Task Delete(string hubName) => this.deleteCallback?.Invoke(hubName) ?? TaskEx.Done; - public string IotHubName => "test"; - public Task SubscribeAsync(string key, Func onChange, Func onDelete, CancellationToken token) { this.changeCallback = onChange; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/app.config b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/app.config index 636bcfafefa..3adb5b2294d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/app.config +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/app.config @@ -1,74 +1,78 @@ - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/CheckpointerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/CheckpointerTest.cs index 233701ff2cd..2e25a1ce490 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/CheckpointerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/CheckpointerTest.cs @@ -16,30 +16,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers public class CheckpointerTest : RoutingUnitTestBase { - static IMessage MessageWithOffset(long offset) => - new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(), offset); - - static class TestAdmitDataSource - { - static readonly IList Data = new List - { - new object[] { MessageWithOffset(long.MinValue), 37L, false }, - new object[] { MessageWithOffset(0L), 37L, false }, - new object[] { MessageWithOffset(37L), 37L, false }, - new object[] { MessageWithOffset(38L), 37L, true }, - new object[] { MessageWithOffset(long.MaxValue), 37L, true }, - - new object[] { MessageWithOffset(long.MinValue), 0L, false }, - new object[] { MessageWithOffset(0L), 0L, false }, - new object[] { MessageWithOffset(37L), 0L, true }, - new object[] { MessageWithOffset(38L), 0L, true }, - new object[] { MessageWithOffset(long.MaxValue), 0L, true }, - }; - - public static IEnumerable TestData => Data; - } - - [Theory, Unit] + [Theory] + [Unit] [MemberData(nameof(TestAdmitDataSource.TestData), MemberType = typeof(TestAdmitDataSource))] public async Task TestAdmit(Message message, long offset, bool expected) { @@ -54,21 +32,8 @@ public async Task TestAdmit(Message message, long offset, bool expected) } } - static class TestCommitDataSource - { - static readonly IList Data = new List - { - new object[] { MessageWithOffset(30), 1000, 1000 }, - new object[] { MessageWithOffset(1000), 1000, 1000 }, - new object[] { MessageWithOffset(1001), 1000, 1001 }, - new object[] { MessageWithOffset(long.MaxValue), 1000, long.MaxValue }, - new object[] { MessageWithOffset(long.MinValue), 1000, 1000 }, - }; - - public static IEnumerable TestData => Data; - } - - [Theory, Unit] + [Theory] + [Unit] [MemberData(nameof(TestCommitDataSource.TestData), MemberType = typeof(TestCommitDataSource))] public async Task TestCommit(Message message, long offset, long expected) { @@ -89,11 +54,13 @@ public async Task TestCommit(Message message, long offset, long expected) { store.Verify(d => d.SetCheckpointDataAsync(It.IsAny(), new CheckpointData(It.IsAny()), It.IsAny()), Times.Never); } + await checkpointer1.CloseAsync(CancellationToken.None); } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCommitMultiple() { var store = new Mock(); @@ -108,28 +75,13 @@ public async Task TestCommitMultiple() } } - static class TestCommitRemainingDataSource - { - static readonly IList Data = new List - { - new object[] { new[] { MessageWithOffset(30), MessageWithOffset(31) }, new Message[0] }, - new object[] { new[] { MessageWithOffset(31), MessageWithOffset(30) }, new Message[0] }, - new object[] { new[] { MessageWithOffset(31), MessageWithOffset(29) }, new[] { MessageWithOffset(30) } }, - new object[] { new[] { MessageWithOffset(31), MessageWithOffset(29) }, new[] { MessageWithOffset(30), MessageWithOffset(28) } }, - new object[] { new[] { MessageWithOffset(31), MessageWithOffset(29) }, new[] { MessageWithOffset(28), MessageWithOffset(30) } }, - new object[] { new[] { MessageWithOffset(0), MessageWithOffset(1) }, new[] { MessageWithOffset(2), MessageWithOffset(3) } }, - new object[] { new[] { MessageWithOffset(2), MessageWithOffset(3) }, new[] { MessageWithOffset(0), MessageWithOffset(1) } }, - }; - - public static IEnumerable TestData => Data; - } - - [Theory, Unit] + [Theory] + [Unit] [MemberData(nameof(TestCommitRemainingDataSource.TestData), MemberType = typeof(TestCommitRemainingDataSource))] public async Task TestCommitWithRemaining(IMessage[] successful, IMessage[] remaining) { var store = new Mock(); - store.Setup(d => d.GetCheckpointDataAsync(It.IsAny(), It.IsAny())).ReturnsAsync(new CheckpointData(- 1)); + store.Setup(d => d.GetCheckpointDataAsync(It.IsAny(), It.IsAny())).ReturnsAsync(new CheckpointData(-1)); ICheckpointer checkpointer = await Checkpointer.CreateAsync("id1", store.Object); await checkpointer.CommitAsync(successful, remaining, Option.None(), Option.None(), CancellationToken.None); @@ -137,7 +89,8 @@ public async Task TestCommitWithRemaining(IMessage[] successful, IMessage[] rema Assert.True(remaining.All(m => checkpointer.Admit(m))); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestConstructor() { var store = new Mock(); @@ -148,7 +101,8 @@ public async Task TestConstructor() await Assert.ThrowsAsync(() => Checkpointer.CreateAsync(null, null)); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClose() { var store = new Mock(); @@ -156,15 +110,15 @@ public async Task TestClose() using (ICheckpointer checkpointer1 = await Checkpointer.CreateAsync("id1", store.Object)) { - await checkpointer1.CommitAsync(new [] { MessageWithOffset(20) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); + await checkpointer1.CommitAsync(new[] { MessageWithOffset(20) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); Assert.Equal(20, checkpointer1.Offset); await checkpointer1.CloseAsync(CancellationToken.None); - await Assert.ThrowsAsync(() => checkpointer1.CommitAsync(new [] { MessageWithOffset(30) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None)); + await Assert.ThrowsAsync(() => checkpointer1.CommitAsync(new[] { MessageWithOffset(30) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None)); // close a second time await checkpointer1.CloseAsync(CancellationToken.None); - await Assert.ThrowsAsync(() => checkpointer1.CommitAsync(new [] { MessageWithOffset(40) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None)); + await Assert.ThrowsAsync(() => checkpointer1.CommitAsync(new[] { MessageWithOffset(40) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None)); Assert.Equal(20, checkpointer1.Offset); bool result = checkpointer1.Admit(MessageWithOffset(30)); @@ -172,7 +126,8 @@ public async Task TestClose() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCancellation() { var store = new Mock(); @@ -186,7 +141,7 @@ public async Task TestCancellation() store.Setup(d => d.SetCheckpointDataAsync(It.IsAny(), It.IsAny(), It.IsAny())).Returns(tcs.Task); - Task result = checkpointer1.CommitAsync(new [] { MessageWithOffset(20) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); + Task result = checkpointer1.CommitAsync(new[] { MessageWithOffset(20) }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); cts.Cancel(); await Assert.ThrowsAsync(() => result); @@ -195,5 +150,58 @@ public async Task TestCancellation() await checkpointer1.CloseAsync(CancellationToken.None); } } + + static IMessage MessageWithOffset(long offset) => + new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(), offset); + + static class TestAdmitDataSource + { + static readonly IList Data = new List + { + new object[] { MessageWithOffset(long.MinValue), 37L, false }, + new object[] { MessageWithOffset(0L), 37L, false }, + new object[] { MessageWithOffset(37L), 37L, false }, + new object[] { MessageWithOffset(38L), 37L, true }, + new object[] { MessageWithOffset(long.MaxValue), 37L, true }, + + new object[] { MessageWithOffset(long.MinValue), 0L, false }, + new object[] { MessageWithOffset(0L), 0L, false }, + new object[] { MessageWithOffset(37L), 0L, true }, + new object[] { MessageWithOffset(38L), 0L, true }, + new object[] { MessageWithOffset(long.MaxValue), 0L, true }, + }; + + public static IEnumerable TestData => Data; + } + + static class TestCommitDataSource + { + static readonly IList Data = new List + { + new object[] { MessageWithOffset(30), 1000, 1000 }, + new object[] { MessageWithOffset(1000), 1000, 1000 }, + new object[] { MessageWithOffset(1001), 1000, 1001 }, + new object[] { MessageWithOffset(long.MaxValue), 1000, long.MaxValue }, + new object[] { MessageWithOffset(long.MinValue), 1000, 1000 }, + }; + + public static IEnumerable TestData => Data; + } + + static class TestCommitRemainingDataSource + { + static readonly IList Data = new List + { + new object[] { new[] { MessageWithOffset(30), MessageWithOffset(31) }, new Message[0] }, + new object[] { new[] { MessageWithOffset(31), MessageWithOffset(30) }, new Message[0] }, + new object[] { new[] { MessageWithOffset(31), MessageWithOffset(29) }, new[] { MessageWithOffset(30) } }, + new object[] { new[] { MessageWithOffset(31), MessageWithOffset(29) }, new[] { MessageWithOffset(30), MessageWithOffset(28) } }, + new object[] { new[] { MessageWithOffset(31), MessageWithOffset(29) }, new[] { MessageWithOffset(28), MessageWithOffset(30) } }, + new object[] { new[] { MessageWithOffset(0), MessageWithOffset(1) }, new[] { MessageWithOffset(2), MessageWithOffset(3) } }, + new object[] { new[] { MessageWithOffset(2), MessageWithOffset(3) }, new[] { MessageWithOffset(0), MessageWithOffset(1) } }, + }; + + public static IEnumerable TestData => Data; + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/LoggedCheckpointer.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/LoggedCheckpointer.cs index 49c11949788..8965be35559 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/LoggedCheckpointer.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/LoggedCheckpointer.cs @@ -6,24 +6,50 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Checkpointers; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; + [ExcludeFromCodeCoverage] + public class LoggedCheckpointTest : RoutingUnitTestBase + { + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3, 4 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 1L); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 4, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 2L); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 4, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 3L); + static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] { 4, 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 4L); + + [Fact] + [Unit] + public async Task SmokeTest() + { + using (var checkpointer = new LoggedCheckpointer(new NullCheckpointer())) + { + Assert.Equal(Checkpointer.InvalidOffset, checkpointer.Offset); + Assert.True(checkpointer.Admit(Message1)); + await checkpointer.CommitAsync(new[] { Message3, Message4 }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); + await checkpointer.CommitAsync(new[] { Message1, Message2 }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); + + Assert.Equal(new List { Message3, Message4, Message1, Message2 }, checkpointer.Processed); + Assert.Equal(Checkpointer.InvalidOffset, checkpointer.Offset); + await checkpointer.CloseAsync(CancellationToken.None); + } + } + } + class LoggedCheckpointer : ICheckpointer { readonly ICheckpointer underlying; - public IList Processed { get; } - public LoggedCheckpointer(ICheckpointer underlying) { this.underlying = underlying; this.Processed = new List(); } + public IList Processed { get; } + public string Id => this.underlying.Id; public long Offset => this.underlying.Offset; @@ -46,6 +72,7 @@ public Task CommitAsync(ICollection successful, ICollection { this.Processed.Add(message); } + return this.underlying.CommitAsync(successful, failed, lastFailedRevivalTime, unhealthySince, token); } @@ -61,29 +88,4 @@ protected virtual void Dispose(bool disposing) } } } - - [ExcludeFromCodeCoverage] - public class LoggedCheckpointTest : RoutingUnitTestBase - { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3, 4}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 1L); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 4, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 2L); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 4, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 3L); - static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] {4, 1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 4L); - - [Fact, Unit] - public async Task SmokeTest() - { - using (var checkpointer = new LoggedCheckpointer(new NullCheckpointer())) - { - Assert.Equal(Checkpointer.InvalidOffset, checkpointer.Offset); - Assert.True(checkpointer.Admit(Message1)); - await checkpointer.CommitAsync(new[] { Message3, Message4 }, new IMessage[] {}, Option.None(), Option.None(), CancellationToken.None); - await checkpointer.CommitAsync(new[] { Message1, Message2 }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); - - Assert.Equal(new List { Message3, Message4, Message1, Message2 }, checkpointer.Processed); - Assert.Equal(Checkpointer.InvalidOffset, checkpointer.Offset); - await checkpointer.CloseAsync(CancellationToken.None); - } - } - } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/MasterCheckpointerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/MasterCheckpointerTest.cs index 6a938225150..c982a90f5bd 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/MasterCheckpointerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/MasterCheckpointerTest.cs @@ -6,16 +6,17 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Checkpointers; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; [ExcludeFromCodeCoverage] public class MasterCheckpointerTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var store = new NullCheckpointStore(); @@ -49,18 +50,20 @@ public async Task SmokeTest() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestNoChildren() { var store = new NullCheckpointStore(); MasterCheckpointer master = await MasterCheckpointer.CreateAsync("checkpointer", store); Assert.Equal(Checkpointer.InvalidOffset, master.Offset); - await master.CommitAsync(new [] { MessageWithOffset(3), MessageWithOffset(1) }, new [] { MessageWithOffset(2) }, Option.None(), Option.None(), CancellationToken.None); + await master.CommitAsync(new[] { MessageWithOffset(3), MessageWithOffset(1) }, new[] { MessageWithOffset(2) }, Option.None(), Option.None(), CancellationToken.None); Assert.Equal(1, master.Offset); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestTwoPropose() { var store = new NullCheckpointStore(); @@ -117,7 +120,8 @@ public async Task TestTwoPropose() Assert.False(checkpointer2.HasOutstanding); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestAdmit() { var store = new NullCheckpointStore(14); @@ -127,7 +131,8 @@ public async Task TestAdmit() Assert.Equal(true, master.Admit(MessageWithOffset(15))); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestOutstanding() { var store = new NullCheckpointStore(1L); @@ -144,7 +149,7 @@ public async Task TestOutstanding() Assert.Equal(1L, master.Offset); // Commit the first message - await checkpointer1.CommitAsync(new[] { message1 }, new IMessage[] {}, Option.None(), Option.None(), CancellationToken.None); + await checkpointer1.CommitAsync(new[] { message1 }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); // Ensure the master represents the checkpointed message offset Assert.True(checkpointer1.HasOutstanding); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointStoreTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointStoreTest.cs index 7af83ffba73..7c97d6f3cf6 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointStoreTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointStoreTest.cs @@ -3,13 +3,14 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers { using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Xunit; public class NullCheckpointStoreTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var store = new NullCheckpointStore(10L); @@ -24,7 +25,8 @@ public async Task SmokeTest() await store.CloseAsync(CancellationToken.None); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestEmptyConstructor() { var store = new NullCheckpointStore(); @@ -32,4 +34,4 @@ public async Task TestEmptyConstructor() Assert.Equal(Checkpointer.InvalidOffset, (await store.GetCheckpointDataAsync("id2", CancellationToken.None)).Offset); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointerTest.cs index 6104ef7b347..02dc86e38a4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/checkpointers/NullCheckpointerTest.cs @@ -5,14 +5,39 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Checkpointers; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; public class NullCheckpointerTest : RoutingUnitTestBase { + [Theory] + [Unit] + [MemberData(nameof(TestAdmitDataSource.TestData), MemberType = typeof(TestAdmitDataSource))] + public void TestAdmit(IMessage message, long offset, bool expected) + { + using (var checkpointer = new NullCheckpointer()) + { + bool result = checkpointer.Admit(message); + Assert.Equal(expected, result); + } + } + + [Theory] + [Unit] + [MemberData(nameof(TestCommitDataSource.TestData), MemberType = typeof(TestCommitDataSource))] + public async Task TestCommit(IMessage message) + { + using (var checkpointer1 = new NullCheckpointer()) + { + await checkpointer1.CommitAsync(new[] { message }, new IMessage[] { }, Option.None(), Option.None(), CancellationToken.None); + Assert.Equal(Checkpointer.InvalidOffset, checkpointer1.Offset); + await checkpointer1.CloseAsync(CancellationToken.None); + } + } + static IMessage MessageWithOffset(long offset) => new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(), new Dictionary(), offset); @@ -36,17 +61,6 @@ static class TestAdmitDataSource public static IEnumerable TestData => Data; } - [Theory, Unit] - [MemberData(nameof(TestAdmitDataSource.TestData), MemberType = typeof(TestAdmitDataSource))] - public void TestAdmit(IMessage message, long offset, bool expected) - { - using (var checkpointer = new NullCheckpointer()) - { - bool result = checkpointer.Admit(message); - Assert.Equal(expected, result); - } - } - static class TestCommitDataSource { static readonly IList Data = new List @@ -60,17 +74,5 @@ static class TestCommitDataSource public static IEnumerable TestData => Data; } - - [Theory, Unit] - [MemberData(nameof(TestCommitDataSource.TestData), MemberType = typeof(TestCommitDataSource))] - public async Task TestCommit(IMessage message) - { - using (var checkpointer1 = new NullCheckpointer()) - { - await checkpointer1.CommitAsync(new[] { message }, new IMessage[] {}, Option.None(), Option.None(), CancellationToken.None); - Assert.Equal(Checkpointer.InvalidOffset, checkpointer1.Offset); - await checkpointer1.CloseAsync(CancellationToken.None); - } - } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/AsyncEndpointExecutorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/AsyncEndpointExecutorTest.cs index 1189551e8fa..dfdd2601dd2 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/AsyncEndpointExecutorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/AsyncEndpointExecutorTest.cs @@ -11,8 +11,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Moq; using Xunit; @@ -20,16 +20,17 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints public class AsyncEndpointExecutorTest : RoutingUnitTestBase { static readonly IMessage Default = new Message(TelemetryMessageSource.Instance, new byte[0], new Dictionary()); - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 1); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 2); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 3); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 1); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 2); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 3); static readonly RetryStrategy MaxRetryStrategy = new FixedInterval(int.MaxValue, TimeSpan.FromMilliseconds(int.MaxValue)); static readonly EndpointExecutorConfig MaxConfig = new EndpointExecutorConfig(Timeout.InfiniteTimeSpan, MaxRetryStrategy, TimeSpan.FromMinutes(5)); static readonly AsyncEndpointExecutorFactory Factory = new AsyncEndpointExecutorFactory(TestConstants.DefaultConfig, TestConstants.DefaultOptions); - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { Assert.Throws(typeof(ArgumentNullException), () => new AsyncEndpointExecutor(null, null, null, null)); @@ -37,7 +38,8 @@ public void TestConstructor() Assert.Throws(typeof(ArgumentNullException), () => new AsyncEndpointExecutor(new TestEndpoint("id"), null, null, null)); } - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var endpoint = new TestEndpoint("id"); @@ -53,6 +55,7 @@ public async Task SmokeTest() { await executor.Invoke(msg); } + await Task.Delay(30); await executor.CloseAsync(); Assert.Equal(3, endpoint.N); @@ -61,7 +64,8 @@ public async Task SmokeTest() Assert.Equal(3, executor.Status.CheckpointerStatus.Proposed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClose() { var endpoint = new TestEndpoint("id"); @@ -76,7 +80,8 @@ public async Task TestClose() await executor.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCancellation() { var endpoint = new StalledEndpoint("id"); @@ -88,7 +93,8 @@ public async Task TestCancellation() Assert.True(running.IsCompleted); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestSetEndpoint() { var endpoint1 = new TestEndpoint("id"); @@ -107,7 +113,8 @@ public async Task TestSetEndpoint() await Assert.ThrowsAsync(() => executor.SetEndpoint(endpoint1)); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestBatchTimeout() { var endpoint1 = new TestEndpoint("id1"); @@ -136,7 +143,8 @@ public async Task TestBatchTimeout() await executor.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestStatus() { var checkpointerStore = new Mock(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/CheckpointerEndpointExecutorFactory.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/CheckpointerEndpointExecutorFactory.cs index 5082651a3b5..ca0f1525b0d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/CheckpointerEndpointExecutorFactory.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/CheckpointerEndpointExecutorFactory.cs @@ -33,6 +33,6 @@ public Task CreateAsync(Endpoint endpoint, ICheckpointer chec public Task CreateAsync(Endpoint endpoint, ICheckpointer checkpointer, EndpointExecutorConfig endpointExecutorConfig) { return this.underlying.CreateAsync(endpoint, checkpointer, endpointExecutorConfig); - } + } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/ConsoleEndpointTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/ConsoleEndpointTest.cs index d66893fb793..a4c8ed87984 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/ConsoleEndpointTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/ConsoleEndpointTest.cs @@ -7,30 +7,32 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using System.IO; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Xunit; [ExcludeFromCodeCoverage] public class ConsoleEndpointTest : RoutingUnitTestBase { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { Assert.Throws(typeof(ArgumentNullException), () => new ConsoleEndpoint(null)); } - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { using (var sw = new StringWriter()) { Console.SetOut(sw); var console = new ConsoleEndpoint("id1"); - IMessage[] messages = new [] { Message1 }; + IMessage[] messages = new[] { Message1 }; IProcessor processor = console.CreateProcessor(); await processor.ProcessAsync(new IMessage[0], CancellationToken.None); await processor.ProcessAsync(messages, CancellationToken.None); @@ -38,7 +40,8 @@ public async Task SmokeTest() string expectedWindows = $"(0) ConsoleEndpoint(id1): {Message1}\r\n"; string expectedLinux = $"(0) ConsoleEndpoint(id1): {Message1}\n"; - Assert.True(expectedWindows.Equals(sw.ToString(), StringComparison.OrdinalIgnoreCase) || + Assert.True( + expectedWindows.Equals(sw.ToString(), StringComparison.OrdinalIgnoreCase) || expectedLinux.Equals(sw.ToString(), StringComparison.OrdinalIgnoreCase)); Assert.Equal(console, processor.Endpoint); Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/FailedEndpoint.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/FailedEndpoint.cs index c15ca8c3ce2..bb53da6b8d2 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/FailedEndpoint.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/FailedEndpoint.cs @@ -15,10 +15,6 @@ public class FailedEndpoint : Endpoint { readonly ErrorDetectionStrategy detectionStrategy; - public Exception Exception { get; } - - public override string Type => nameof(FailedEndpoint); - public FailedEndpoint(string id) : this(id, new Exception()) { @@ -41,6 +37,10 @@ public FailedEndpoint(string id, string name, string iotHubName, Exception excep this.detectionStrategy = detectionStrategy; } + public Exception Exception { get; } + + public override string Type => nameof(FailedEndpoint); + public override IProcessor CreateProcessor() => new Processor(this); public override void LogUserMetrics(long messageCount, long latencyInMs) @@ -51,15 +51,15 @@ class Processor : IProcessor { readonly FailedEndpoint endpoint; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => this.endpoint.detectionStrategy; - public Processor(FailedEndpoint endpoint) { this.endpoint = endpoint; } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => this.endpoint.detectionStrategy; + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); @@ -76,7 +76,8 @@ public Task> ProcessAsync(ICollection messages, [ExcludeFromCodeCoverage] public class FailedEndpointTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var cts = new CancellationTokenSource(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/NullEndpointTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/NullEndpointTest.cs index 2097815f629..2b6c9f423de 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/NullEndpointTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/NullEndpointTest.cs @@ -5,8 +5,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Xunit; @@ -16,7 +16,8 @@ public class NullEndpointTest : RoutingUnitTestBase static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var endpoint = new NullEndpoint("endpoint1"); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/PartialFailureEndpoint.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/PartialFailureEndpoint.cs index 7f467adf98a..8ff461df692 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/PartialFailureEndpoint.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/PartialFailureEndpoint.cs @@ -9,14 +9,40 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; - class PartialFailureEndpoint : Endpoint + [ExcludeFromCodeCoverage] + public class PartialFailureEndpointTest : RoutingUnitTestBase { - public Exception Exception { get; } + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3, 4 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 4, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 4, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] { 4, 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + + [Fact] + [Unit] + public async Task SmokeTest() + { + var endpoint = new PartialFailureEndpoint("id1"); + IProcessor processor = endpoint.CreateProcessor(); + Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); + + Assert.Equal(endpoint, processor.Endpoint); + ISinkResult result = await processor.ProcessAsync(new IMessage[] { }, CancellationToken.None); + Assert.Equal(new IMessage[0], result.Succeeded); + + IMessage[] messages = new[] { Message1, Message2, Message3, Message4 }; + ISinkResult result2 = await processor.ProcessAsync(messages, CancellationToken.None); + Assert.Equal(new[] { Message1, Message2 }, result2.Succeeded); + Assert.Equal(new[] { Message3, Message4 }, result2.Failed); + } + } + + class PartialFailureEndpoint : Endpoint + { public PartialFailureEndpoint(string id) : this(id, new Exception()) { @@ -33,6 +59,8 @@ public PartialFailureEndpoint(string id, string name, string iotHubName, Excepti this.Exception = exception; } + public Exception Exception { get; } + public override string Type => nameof(PartialFailureEndpoint); public override void LogUserMetrics(long messageCount, long latencyInMs) @@ -46,15 +74,15 @@ class PartialFailureProcessor : IProcessor { readonly PartialFailureEndpoint endpoint; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); - public PartialFailureProcessor(PartialFailureEndpoint endpoint) { this.endpoint = endpoint; } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); @@ -72,37 +100,11 @@ public Task> ProcessAsync(ICollection messages, IMessage[] failed = messages.Skip(messages.Count / 2).ToArray(); result = new SinkResult(successful, failed, new SendFailureDetails(FailureKind.InternalError, this.endpoint.Exception)); } + return Task.FromResult(result); } public Task CloseAsync(CancellationToken token) => TaskEx.Done; } } - - [ExcludeFromCodeCoverage] - public class PartialFailureEndpointTest : RoutingUnitTestBase - { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3, 4}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 4, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 4, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] {4, 1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - - [Fact, Unit] - public async Task SmokeTest() - { - var endpoint = new PartialFailureEndpoint("id1"); - IProcessor processor = endpoint.CreateProcessor(); - - Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); - - Assert.Equal(endpoint, processor.Endpoint); - ISinkResult result = await processor.ProcessAsync(new IMessage[] {}, CancellationToken.None); - Assert.Equal(new IMessage[0], result.Succeeded); - - IMessage[] messages = new[] { Message1, Message2, Message3, Message4 }; - ISinkResult result2 = await processor.ProcessAsync(messages, CancellationToken.None); - Assert.Equal(new[] {Message1, Message2}, result2.Succeeded); - Assert.Equal(new[] {Message3, Message4}, result2.Failed); - } - } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/RevivableEndpoint.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/RevivableEndpoint.cs index 7b4aeaa01cb..f4dbc2db169 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/RevivableEndpoint.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/RevivableEndpoint.cs @@ -8,18 +8,57 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; - class RevivableEndpoint : Endpoint + [ExcludeFromCodeCoverage] + public class RevivableEndpointTest : RoutingUnitTestBase { - public IList Processed { get; } + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); - public Exception Exception { get; } + [Fact] + [Unit] + public async Task SmokeTest() + { + var endpoint = new RevivableEndpoint("id1"); + IProcessor processor = endpoint.CreateProcessor(); - public bool Failing { get; set; } + Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); + + Assert.Equal(endpoint, processor.Endpoint); + Assert.Equal(new List(), endpoint.Processed); + Assert.Equal(string.Empty, endpoint.IotHubName); + + ISinkResult result = await processor.ProcessAsync(new IMessage[0], CancellationToken.None); + Assert.Equal(new IMessage[0], result.Succeeded); + Assert.Equal(new List(), endpoint.Processed); + + IMessage[] messages = new[] { Message1, Message2, Message3 }; + ISinkResult result2 = await processor.ProcessAsync(messages, CancellationToken.None); + Assert.Equal(new[] { Message1, Message2, Message3 }, result2.Succeeded); + Assert.Equal(new List { Message1, Message2, Message3 }, endpoint.Processed); + + // set to failing + endpoint.Failing = true; + ISinkResult result3 = await processor.ProcessAsync(messages, CancellationToken.None); + Assert.True(result3.SendFailureDetails.HasValue); + Assert.Equal(new List { Message1, Message2, Message3 }, endpoint.Processed); + + // revive + endpoint.Failing = false; + ISinkResult result4 = await processor.ProcessAsync(messages, CancellationToken.None); + Assert.Equal(new[] { Message1, Message2, Message3 }, result4.Succeeded); + Assert.Equal(new List { Message1, Message2, Message3, Message1, Message2, Message3 }, endpoint.Processed); + + await processor.CloseAsync(CancellationToken.None); + } + } + class RevivableEndpoint : Endpoint + { public RevivableEndpoint(string id) : this(id, new Exception()) { @@ -38,6 +77,12 @@ public RevivableEndpoint(string id, string name, string iotHubName, Exception ex this.Failing = false; } + public IList Processed { get; } + + public Exception Exception { get; } + + public bool Failing { get; set; } + public override string Type => nameof(RevivableEndpoint); public override void LogUserMetrics(long messageCount, long latencyInMs) @@ -50,15 +95,15 @@ class Processor : IProcessor { readonly RevivableEndpoint endpoint; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true ); - public Processor(RevivableEndpoint endpoint) { this.endpoint = endpoint; } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); @@ -78,54 +123,11 @@ public Task> ProcessAsync(ICollection messages, result = new SinkResult(messages); } + return Task.FromResult(result); } public Task CloseAsync(CancellationToken token) => TaskEx.Done; } } - - [ExcludeFromCodeCoverage] - public class RevivableEndpointTest : RoutingUnitTestBase - { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - - [Fact, Unit] - public async Task SmokeTest() - { - var endpoint = new RevivableEndpoint("id1"); - IProcessor processor = endpoint.CreateProcessor(); - - Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); - - Assert.Equal(endpoint, processor.Endpoint); - Assert.Equal(new List(), endpoint.Processed); - Assert.Equal(string.Empty, endpoint.IotHubName); - - ISinkResult result = await processor.ProcessAsync(new IMessage[0], CancellationToken.None); - Assert.Equal(new IMessage[0], result.Succeeded); - Assert.Equal(new List(), endpoint.Processed); - - IMessage[] messages = new[] { Message1, Message2, Message3 }; - ISinkResult result2 = await processor.ProcessAsync(messages, CancellationToken.None); - Assert.Equal(new[] { Message1, Message2, Message3 }, result2.Succeeded); - Assert.Equal(new List { Message1, Message2, Message3 }, endpoint.Processed); - - // set to failing - endpoint.Failing = true; - ISinkResult result3 = await processor.ProcessAsync(messages, CancellationToken.None); - Assert.True(result3.SendFailureDetails.HasValue); - Assert.Equal(new List { Message1, Message2, Message3 }, endpoint.Processed); - - // revive - endpoint.Failing = false; - ISinkResult result4 = await processor.ProcessAsync(messages, CancellationToken.None); - Assert.Equal(new[] { Message1, Message2, Message3 }, result4.Succeeded); - Assert.Equal(new List { Message1, Message2, Message3, Message1, Message2, Message3 }, endpoint.Processed); - - await processor.CloseAsync(CancellationToken.None); - } - } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StalledEndpoint.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StalledEndpoint.cs index 7f30e727f1f..94f4e42dbfb 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StalledEndpoint.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StalledEndpoint.cs @@ -10,6 +10,32 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; + public class StalledEndpointTest : RoutingUnitTestBase + { + [Fact] + [Unit] + public void SmokeTest() + { + var endpoint = new StalledEndpoint("id1"); + IProcessor processor = endpoint.CreateProcessor(); + + Assert.Equal(endpoint, processor.Endpoint); + Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); + Assert.Equal(string.Empty, endpoint.IotHubName); + + var cts = new CancellationTokenSource(); + Task> result = processor.ProcessAsync(new IMessage[] { }, cts.Token); + Assert.False(result.IsCompleted); + Assert.False(result.IsCanceled); + Assert.False(result.IsFaulted); + + cts.Cancel(); + Assert.True(result.IsCompleted); + Assert.True(result.IsCanceled); + Assert.False(result.IsFaulted); + } + } + class StalledEndpoint : Endpoint { public StalledEndpoint(string id) @@ -34,15 +60,15 @@ class Processor : IProcessor { readonly StalledEndpoint endpoint; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); - public Processor(StalledEndpoint endpoint) { this.endpoint = endpoint; } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); @@ -56,29 +82,4 @@ public Task> ProcessAsync(ICollection message, C public Task CloseAsync(CancellationToken token) => TaskEx.Done; } } - - public class StalledEndpointTest : RoutingUnitTestBase - { - [Fact, Unit] - public void SmokeTest() - { - var endpoint = new StalledEndpoint("id1"); - IProcessor processor = endpoint.CreateProcessor(); - - Assert.Equal(endpoint, processor.Endpoint); - Assert.True(processor.ErrorDetectionStrategy.IsTransient(new Exception())); - Assert.Equal(string.Empty, endpoint.IotHubName); - - var cts = new CancellationTokenSource(); - Task> result = processor.ProcessAsync(new IMessage[] { }, cts.Token); - Assert.False(result.IsCompleted); - Assert.False(result.IsCanceled); - Assert.False(result.IsFaulted); - - cts.Cancel(); - Assert.True(result.IsCompleted); - Assert.True(result.IsCanceled); - Assert.False(result.IsFaulted); - } - } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StoringAsyncEndpointExecutorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StoringAsyncEndpointExecutorTest.cs index 3615a7f14e9..727c8b53356 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StoringAsyncEndpointExecutorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/StoringAsyncEndpointExecutorTest.cs @@ -37,7 +37,7 @@ public async Task InvokeTest() await storingAsyncEndpointExecutor.Invoke(message); } - // Assert - Check that the message store received the messages sent to invoke. + // Assert - Check that the message store received the messages sent to invoke. List storeMessages = messageStore.GetReceivedMessagesForEndpoint(EndpointId); Assert.NotNull(storeMessages); Assert.Equal(MessagesCount, storeMessages.Count); @@ -48,19 +48,19 @@ public async Task InvokeTest() Assert.Equal($"value{i}", message.Properties[$"key{i}"]); } - // Assert - Make sure no additional / duplicate messages were sent. + // Assert - Make sure no additional / duplicate messages were sent. storeMessages = messageStore.GetReceivedMessagesForEndpoint(EndpointId); Assert.NotNull(storeMessages); Assert.Equal(10, storeMessages.Count); - // Act - Send messages again to Invoke. + // Act - Send messages again to Invoke. messages = GetNewMessages(MessagesCount, MessagesCount); foreach (IMessage message in messages) { await storingAsyncEndpointExecutor.Invoke(message); } - // Assert - Make sure the store now has the old and the new messages. + // Assert - Make sure the store now has the old and the new messages. storeMessages = messageStore.GetReceivedMessagesForEndpoint(EndpointId); Assert.NotNull(storeMessages); Assert.Equal(MessagesCount * 2, storeMessages.Count); @@ -94,7 +94,7 @@ public async Task PumpMessagesTest() await Task.Delay(TimeSpan.FromSeconds(10)); - // Assert - Make sure the endpoint received all the messages. + // Assert - Make sure the endpoint received all the messages. Assert.Equal(MessagesCount, endpoint.N); for (int i = 0; i < MessagesCount; i++) { @@ -127,7 +127,7 @@ public async Task PumpMessagesWithLargeIncomingBatchTest() await Task.Delay(TimeSpan.FromSeconds(10)); - // Assert - Make sure the endpoint received all the messages. + // Assert - Make sure the endpoint received all the messages. Assert.Equal(MessagesCount, endpoint.N); for (int i = 0; i < MessagesCount; i++) { @@ -175,12 +175,33 @@ static IEnumerable GetNewMessages(int count, int indexStart) } } + class CheckpointStore : ICheckpointStore + { + readonly Dictionary checkpointDatas = new Dictionary(); + + public Task GetCheckpointDataAsync(string id, CancellationToken token) + { + CheckpointData checkpointData = this.checkpointDatas.ContainsKey(id) + ? this.checkpointDatas[id] + : new CheckpointData(Checkpointer.InvalidOffset); + return Task.FromResult(checkpointData); + } + + public Task> GetAllCheckpointDataAsync(CancellationToken token) => Task.FromResult((IDictionary)this.checkpointDatas); + + public Task SetCheckpointDataAsync(string id, CheckpointData checkpointData, CancellationToken token) + { + this.checkpointDatas[id] = checkpointData; + return Task.CompletedTask; + } + + public Task CloseAsync(CancellationToken token) => Task.CompletedTask; + } + class TestMessageStore : IMessageStore { readonly ConcurrentDictionary endpointQueues = new ConcurrentDictionary(); - TestMessageQueue GetQueue(string endpointId) => this.endpointQueues.GetOrAdd(endpointId, new TestMessageQueue()); - public void Dispose() { } @@ -211,11 +232,13 @@ public Task RemoveEndpoint(string endpointId) public List GetReceivedMessagesForEndpoint(string endpointId) => this.GetQueue(endpointId).Queue; + TestMessageQueue GetQueue(string endpointId) => this.endpointQueues.GetOrAdd(endpointId, new TestMessageQueue()); + class TestMessageQueue : IMessageIterator { - int index; readonly List queue = new List(); readonly AsyncLock queueLock = new AsyncLock(); + int index; public List Queue => this.queue; @@ -237,33 +260,11 @@ public async Task> GetNext(int batchSize) { batch.Add(this.queue[this.index]); } + return batch; } } } } - - class CheckpointStore : ICheckpointStore - { - readonly Dictionary checkpointDatas = new Dictionary(); - - public Task GetCheckpointDataAsync(string id, CancellationToken token) - { - CheckpointData checkpointData = this.checkpointDatas.ContainsKey(id) - ? this.checkpointDatas[id] - : new CheckpointData(Checkpointer.InvalidOffset); - return Task.FromResult(checkpointData); - } - - public Task> GetAllCheckpointDataAsync(CancellationToken token) => Task.FromResult((IDictionary)this.checkpointDatas); - - public Task SetCheckpointDataAsync(string id, CheckpointData checkpointData, CancellationToken token) - { - this.checkpointDatas[id] = checkpointData; - return Task.CompletedTask; - } - - public Task CloseAsync(CancellationToken token) => Task.CompletedTask; - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/SyncEndpointExecutorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/SyncEndpointExecutorTest.cs index a6fbda33822..d96f08c6d38 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/SyncEndpointExecutorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/SyncEndpointExecutorTest.cs @@ -11,8 +11,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Moq; using Xunit; @@ -20,13 +20,14 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints public class SyncEndpointExecutorTest : RoutingUnitTestBase { static readonly IMessage Default = new Message(TelemetryMessageSource.Instance, new byte[0], new Dictionary()); - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 1); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 2); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 3); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 1); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 2); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 3); static readonly SyncEndpointExecutorFactory Factory = new SyncEndpointExecutorFactory(TestConstants.DefaultConfig); - [Fact, Unit] + [Fact] + [Unit] public void TestConstructor() { Assert.Throws(typeof(ArgumentNullException), () => new SyncEndpointExecutor(null, null)); @@ -34,7 +35,8 @@ public void TestConstructor() Assert.Throws(typeof(ArgumentNullException), () => new SyncEndpointExecutor(new TestEndpoint("id"), null)); } - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var endpoint = new TestEndpoint("id"); @@ -50,6 +52,7 @@ public async Task SmokeTest() { await executor.Invoke(msg); } + await executor.CloseAsync(); Assert.Equal(3, endpoint.N); Assert.Equal(expected, endpoint.Processed); @@ -57,7 +60,8 @@ public async Task SmokeTest() Assert.Equal(3, executor.Status.CheckpointerStatus.Proposed); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCancellation() { var retryStrategy = new FixedInterval(10, TimeSpan.FromSeconds(1)); @@ -77,7 +81,8 @@ public async Task TestCancellation() Assert.True(running.IsCompleted); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClose() { var endpoint = new TestEndpoint("id"); @@ -95,7 +100,8 @@ public async Task TestClose() await Assert.ThrowsAsync(() => executor.SetEndpoint(endpoint)); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestSetEndpoint() { var endpoint1 = new TestEndpoint("id1"); @@ -111,26 +117,27 @@ public async Task TestSetEndpoint() await executor.Invoke(Message1); await executor.Invoke(Message1); - Assert.Equal(new List {Message1, Message1}, endpoint1.Processed); + Assert.Equal(new List { Message1, Message1 }, endpoint1.Processed); Assert.Equal(new List(), endpoint2.Processed); Assert.Equal(new List(), endpoint3.Processed); await executor.SetEndpoint(endpoint2); - Assert.Equal(new List {Message1, Message1}, endpoint1.Processed); + Assert.Equal(new List { Message1, Message1 }, endpoint1.Processed); Assert.Equal(new List(), endpoint2.Processed); Assert.Equal(new List(), endpoint3.Processed); await executor.Invoke(Message2); await executor.Invoke(Message3); - Assert.Equal(new List {Message1, Message1}, endpoint1.Processed); - Assert.Equal(new List {Message2, Message3}, endpoint2.Processed); + Assert.Equal(new List { Message1, Message1 }, endpoint1.Processed); + Assert.Equal(new List { Message2, Message3 }, endpoint2.Processed); Assert.Equal(new List(), endpoint3.Processed); await executor.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpoint() { var endpoint1 = new TestEndpoint("id1"); @@ -142,7 +149,7 @@ public async Task TestCheckpoint() IEndpointExecutor executor1 = new SyncEndpointExecutor(endpoint1, checkpointer.Object); await executor1.Invoke(Message1); - checkpointer.Verify(c => c.CommitAsync(new [] { Message1 }, new IMessage[0], It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + checkpointer.Verify(c => c.CommitAsync(new[] { Message1 }, new IMessage[0], It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); var endpoint2 = new NullEndpoint("id2"); var checkpointer2 = new Mock(); @@ -159,7 +166,8 @@ public async Task TestCheckpoint() await executor2.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestProcessorFailure() { var endpoint1 = new FailedEndpoint("id1", new OperationCanceledException()); @@ -172,4 +180,4 @@ public async Task TestProcessorFailure() await Assert.ThrowsAsync(() => executor2.Invoke(Message1)); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/TestEndpoint.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/TestEndpoint.cs index 95e898957b9..77ea30816db 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/TestEndpoint.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/TestEndpoint.cs @@ -7,16 +7,12 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; public class TestEndpoint : Endpoint { - public int N => this.Processed.Count; - - public IList Processed { get; } - public TestEndpoint(string id) : this(id, id, string.Empty) { @@ -28,6 +24,10 @@ public TestEndpoint(string id, string name, string iotHubName) this.Processed = new List(); } + public int N => this.Processed.Count; + + public IList Processed { get; } + public override string Type => nameof(TestEndpoint); public override void LogUserMetrics(long messageCount, long latencyInMs) @@ -36,19 +36,21 @@ public override void LogUserMetrics(long messageCount, long latencyInMs) public override IProcessor CreateProcessor() => new Processor(this); + public override string ToString() => $"TestEndpoint({this.Id})"; + class Processor : IProcessor { readonly TestEndpoint endpoint; - public Endpoint Endpoint => this.endpoint; - - public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); - public Processor(TestEndpoint endpoint) { this.endpoint = endpoint; } + public Endpoint Endpoint => this.endpoint; + + public ITransientErrorDetectionStrategy ErrorDetectionStrategy => new ErrorDetectionStrategy(_ => true); + public Task> ProcessAsync(IMessage message, CancellationToken token) => this.ProcessAsync(new[] { message }, token); @@ -58,23 +60,23 @@ public Task> ProcessAsync(ICollection messages, { this.endpoint.Processed.Add(message); } + ISinkResult result = new SinkResult(messages); return Task.FromResult(result); } public Task CloseAsync(CancellationToken token) => TaskEx.Done; } - - public override string ToString() => $"TestEndpoint({this.Id})"; } public class TestEndpointTest : RoutingUnitTestBase { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var endpoint = new TestEndpoint("id1", "name1", "hub1"); @@ -90,7 +92,7 @@ public async Task SmokeTest() Assert.Equal(new IMessage[0], result.Succeeded); Assert.Equal(new List(), endpoint.Processed); - IMessage[] messages = new [] { Message1, Message2, Message3 }; + IMessage[] messages = new[] { Message1, Message2, Message3 }; ISinkResult result2 = await processor.ProcessAsync(messages, CancellationToken.None); Assert.Equal(new[] { Message1, Message2, Message3 }, result2.Succeeded); Assert.Equal(new List { Message1, Message2, Message3 }, endpoint.Processed); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/EndpointExecutorFsmTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/EndpointExecutorFsmTest.cs index 4585527e0cc..df1017e4a59 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/EndpointExecutorFsmTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/EndpointExecutorFsmTest.cs @@ -12,28 +12,26 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints.StateMachine using Microsoft.Azure.Devices.Routing.Core.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine; - using Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Test.Checkpointers; using Microsoft.Azure.Devices.Routing.Core.Test.Util; + using Microsoft.Azure.Devices.Routing.Core.Util; using Moq; using Xunit; [ExcludeFromCodeCoverage] public class EndpointExecutorFsmTest : RoutingUnitTestBase { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3, 4}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 1); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 4, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 2); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {3, 4, 1, 2}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 3); - static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] {4, 1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }, 4); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3, 4 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 1); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 4, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 2); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 3, 4, 1, 2 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 3); + static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] { 4, 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, 4); static readonly RetryStrategy MaxRetryStrategy = new FixedInterval(int.MaxValue, TimeSpan.FromMilliseconds(int.MaxValue)); static readonly EndpointExecutorConfig MaxConfig = new EndpointExecutorConfig(Timeout.InfiniteTimeSpan, MaxRetryStrategy, TimeSpan.FromMinutes(5)); - static IMessage MessageWithOffset(long offset) => - new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(), offset); - - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpoint() { // Test checkpoint @@ -45,7 +43,7 @@ public async Task TestCheckpoint() var machine1 = new EndpointExecutorFsm(endpoint1, checkpointer.Object, MaxConfig); await machine1.RunAsync(Commands.SendMessage(Message1, Message2)); - checkpointer.Verify(c => c.CommitAsync(new [] { Message1, Message2 }, new IMessage[0], Option.None(), Option.None(), It.IsAny()), Times.Exactly(1)); + checkpointer.Verify(c => c.CommitAsync(new[] { Message1, Message2 }, new IMessage[0], Option.None(), Option.None(), It.IsAny()), Times.Exactly(1)); await machine1.CloseAsync(); // Test no checkpoint @@ -61,7 +59,8 @@ public async Task TestCheckpoint() await machine2.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpointAdmit() { var endpoint = new TestEndpoint("id1"); @@ -88,7 +87,8 @@ public async Task TestCheckpointAdmit() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpointDead() { var endpoint = new FailedEndpoint("id1", new Exception()); @@ -124,7 +124,8 @@ public async Task TestCheckpointDead() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpointException() { // Test no throw @@ -182,7 +183,8 @@ public async Task TestCheckpointException() await machine3.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpointPartialFailureToDead() { var endpoint1 = new PartialFailureEndpoint("id1", new InvalidOperationException("test")); @@ -206,8 +208,8 @@ public async Task TestCheckpointPartialFailureToDead() } } - - [Fact, Unit] + [Fact] + [Unit] public async Task TestCheckpointPartialFailureToSuccess() { var endpoint1 = new PartialFailureEndpoint("id1", new InvalidOperationException("test")); @@ -229,7 +231,8 @@ public async Task TestCheckpointPartialFailureToSuccess() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestSetEndpoint() { var endpoint1 = new TestEndpoint("id1"); @@ -266,7 +269,8 @@ public async Task TestSetEndpoint() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFailingEndpoint() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -304,7 +308,8 @@ public async Task TestFailingEndpoint() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFailingEndpointUpdate() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -346,7 +351,8 @@ public async Task TestFailingEndpointUpdate() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFailingEndpointClose() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -376,7 +382,8 @@ public async Task TestFailingEndpointClose() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestTimeoutIsFail() { var endpoint = new StalledEndpoint("id1"); @@ -395,7 +402,8 @@ public async Task TestTimeoutIsFail() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDying() { var endpoint1 = new RevivableEndpoint("id1", new Exception("endpoint failed")); @@ -450,7 +458,8 @@ public async Task TestDying() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDie() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -487,7 +496,8 @@ public async Task TestDie() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDieToThrow() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -523,7 +533,8 @@ public async Task TestDieToThrow() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestRetryTimer() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -550,7 +561,8 @@ public async Task TestRetryTimer() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestReviveToSuccess() { var endpoint1 = new RevivableEndpoint("id1", new Exception("endpoint failed")); @@ -606,7 +618,8 @@ public async Task TestReviveToSuccess() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestReviveToFail() { var endpoint1 = new RevivableEndpoint("id1", new Exception("endpoint failed")); @@ -657,7 +670,8 @@ public async Task TestReviveToFail() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestToDeadOnNonTransient() { var endpoint1 = new FailedEndpoint("id1", new Exception("nontransient")); @@ -680,7 +694,8 @@ public async Task TestToDeadOnNonTransient() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDead() { var endpoint1 = new FailedEndpoint("id1", new Exception("endpoint failed")); @@ -724,7 +739,8 @@ public async Task TestDead() await machine.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDeadCheckpointException() { // Test operation canceled exception - no throw @@ -741,7 +757,7 @@ public async Task TestDeadCheckpointException() await machine1.RunAsync(Commands.SendMessage(Message1)); await machine1.RunAsync(Commands.Retry); Assert.Equal(State.DeadIdle, machine1.Status.State); - checkpointer1.Verify(c => c.CommitAsync(new [] { Message1 }, It.IsAny>(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); + checkpointer1.Verify(c => c.CommitAsync(new[] { Message1 }, It.IsAny>(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Exactly(1)); await machine1.CloseAsync(); } @@ -764,7 +780,8 @@ public async Task TestDeadCheckpointException() await machine2.CloseAsync(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestErrorDetectionStrategy() { var detectionStrategy = new ErrorDetectionStrategy(ex => ex.GetType() != typeof(InvalidOperationException)); @@ -802,7 +819,8 @@ public async Task TestErrorDetectionStrategy() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDeadStatus() { var checkpointerStore = new Mock(); @@ -821,36 +839,36 @@ public async Task TestDeadStatus() using (var machine = new EndpointExecutorFsm(endpoint, checkpointer, config)) { // TODO find a way to test this without a delay - //await machine.RunAsync(Commands.SendMessage(Message1)); + // await machine.RunAsync(Commands.SendMessage(Message1)); //// checkpoint should have dropped and moved the offset - //Assert.Equal(0, endpoint.N); // endpoint should not get the message as it is dead - //EndpointExecutorStatus status = machine.Status; - //Assert.Equal("endpoint1", status.Id); - //Assert.Equal(short.MaxValue, status.RetryAttempts); - //Assert.Equal(State.DeadIdle, status.State); - //Assert.NotEqual(DateTime.UtcNow, status.LastFailedRevivalTime.GetOrElse(DateTime.MinValue)); - //Assert.True(DateTime.UtcNow > status.LastFailedRevivalTime.GetOrElse(DateTime.MinValue)); - //Assert.Equal("checkpoint.id1", status.CheckpointerStatus.Id); - //Assert.Equal(1, status.CheckpointerStatus.Offset); - - //await Task.Delay(TimeSpan.FromSeconds(5)); - //await machine.RunAsync(Commands.SendMessage(Message2)); + // Assert.Equal(0, endpoint.N); // endpoint should not get the message as it is dead + // EndpointExecutorStatus status = machine.Status; + // Assert.Equal("endpoint1", status.Id); + // Assert.Equal(short.MaxValue, status.RetryAttempts); + // Assert.Equal(State.DeadIdle, status.State); + // Assert.NotEqual(DateTime.UtcNow, status.LastFailedRevivalTime.GetOrElse(DateTime.MinValue)); + // Assert.True(DateTime.UtcNow > status.LastFailedRevivalTime.GetOrElse(DateTime.MinValue)); + // Assert.Equal("checkpoint.id1", status.CheckpointerStatus.Id); + // Assert.Equal(1, status.CheckpointerStatus.Offset); + + // await Task.Delay(TimeSpan.FromSeconds(5)); + // await machine.RunAsync(Commands.SendMessage(Message2)); // Check with revival now - //Assert.Equal(1, endpoint.N); // endpoint gets the message with revival - //status = machine.Status; - //Assert.Equal("endpoint1", status.Id); - //Assert.Equal(0, status.RetryAttempts); - //Assert.Equal(State.Idle, status.State); - //Assert.Equal(Checkpointer.DateTimeMinValue, status.LastFailedRevivalTime.GetOrElse(Checkpointer.DateTimeMinValue)); - //Assert.Equal("checkpoint.id1", status.CheckpointerStatus.Id); - //Assert.Equal(2, status.CheckpointerStatus.Offset); - + // Assert.Equal(1, endpoint.N); // endpoint gets the message with revival + // status = machine.Status; + // Assert.Equal("endpoint1", status.Id); + // Assert.Equal(0, status.RetryAttempts); + // Assert.Equal(State.Idle, status.State); + // Assert.Equal(Checkpointer.DateTimeMinValue, status.LastFailedRevivalTime.GetOrElse(Checkpointer.DateTimeMinValue)); + // Assert.Equal("checkpoint.id1", status.CheckpointerStatus.Id); + // Assert.Equal(2, status.CheckpointerStatus.Offset); await machine.CloseAsync(); } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestReliableDeadStatus() { var checkpointerStore = new Mock(); @@ -886,13 +904,14 @@ public async Task TestReliableDeadStatus() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestInvalidMessages() { // Check that all successful and invalid messages are checkpointed Checkpointer checkpointer = await Checkpointer.CreateAsync("checkpointer", new NullCheckpointStore(0L)); - var result = new SinkResult(new List { Message2 }, new List(), new List> { new InvalidDetails(Message3, FailureKind.MaxMessageSizeExceeded), new InvalidDetails(Message1, FailureKind.MaxMessageSizeExceeded)}, new SendFailureDetails(FailureKind.InternalError, new Exception())); + var result = new SinkResult(new List { Message2 }, new List(), new List> { new InvalidDetails(Message3, FailureKind.MaxMessageSizeExceeded), new InvalidDetails(Message1, FailureKind.MaxMessageSizeExceeded) }, new SendFailureDetails(FailureKind.InternalError, new Exception())); var processor = new Mock(); processor.Setup(p => p.ErrorDetectionStrategy).Returns(new ErrorDetectionStrategy(_ => true)); processor.Setup(p => p.ProcessAsync(It.IsAny>(), It.IsAny())).ReturnsAsync(result); @@ -907,18 +926,21 @@ public async Task TestInvalidMessages() Assert.Equal(3L, checkpointer.Offset); } + static IMessage MessageWithOffset(long offset) => + new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary(), offset); + class InvalidEndpoint : Endpoint { readonly Func processorFactory; - public override string Type => "InvalidEndpoint"; - public InvalidEndpoint(string id, Func processorFactory) : base(id) { this.processorFactory = processorFactory; } + public override string Type => "InvalidEndpoint"; + public override IProcessor CreateProcessor() => this.processorFactory(); public override void LogUserMetrics(long messageCount, long latencyInMs) @@ -926,4 +948,4 @@ public override void LogUserMetrics(long messageCount, long latencyInMs) } } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/StateCommandPairTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/StateCommandPairTest.cs index 8b317a1a6ab..ec6fca10aac 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/StateCommandPairTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/endpoints/statemachine/StateCommandPairTest.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Test.Endpoints.StateMachine { - using Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Endpoints.StateMachine; using Xunit; public class StateCommandPairTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void TestEqual() { var pair1 = new StateCommandPair(State.Idle, CommandType.SendMessage); @@ -25,11 +26,12 @@ public void TestEqual() Assert.False(pair1.Equals(new object())); } - [Fact, Unit] + [Fact] + [Unit] public void TestShow() { var pair = new StateCommandPair(State.Idle, CommandType.SendMessage); Assert.Equal("StateCommandPair(Idle, SendMessage)", pair.ToString()); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/JsonPathValidatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/JsonPathValidatorTest.cs index 34a0c20d728..e7fbe416d3a 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/JsonPathValidatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/JsonPathValidatorTest.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Test.JsonPath { - using Microsoft.Azure.Devices.Routing.Core.Query.JsonPath; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query.JsonPath; using Xunit; public class JsonPathValidatorTest { - [Theory, Unit] + [Theory] + [Unit] [InlineData("root")] [InlineData("ROOT")] [InlineData("root.level1")] @@ -22,7 +23,8 @@ public void JsonPathValidator_Success(string jsonPath) Assert.True(JsonPathValidator.IsSupportedJsonPath(jsonPath, out _)); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("")] [InlineData("root.")] [InlineData("ROOT[")] @@ -44,7 +46,8 @@ public void JsonPathValidator_Fail(string jsonPath) Assert.NotEmpty(errorDetails); } - [Fact, Unit] + [Fact] + [Unit] public void JsonPathValidator_Debug() { string jsonPath = "message.Weather.HistoricalData[0].Temperature[1]"; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/TwinChangeJsonPathValidatorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/TwinChangeJsonPathValidatorTest.cs index 6613cd22a39..869092fe424 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/TwinChangeJsonPathValidatorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/jsonpath/TwinChangeJsonPathValidatorTest.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Test.JsonPath { - using Microsoft.Azure.Devices.Routing.Core.Query.JsonPath; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query.JsonPath; using Xunit; public class TwinChangeJsonPathValidatorTest { - [Theory, Unit] + [Theory] + [Unit] [InlineData("properties.reported.property1")] [InlineData("properties.desired.property1")] [InlineData("properties.reported.root.level1")] @@ -18,7 +19,8 @@ public void TwinChangeJsonPathValidator_Success(string jsonPath) Assert.True(TwinChangeJsonPathValidator.IsSupportedJsonPath(jsonPath, out _)); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("")] [InlineData("123abc")] [InlineData("string with spaces")] diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/perfcounters/NullRoutingPerfCounter.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/perfcounters/NullRoutingPerfCounter.cs index b9d0bad0bbf..a5439a66d7e 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/perfcounters/NullRoutingPerfCounter.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/perfcounters/NullRoutingPerfCounter.cs @@ -8,6 +8,7 @@ public bool LogEventProcessingLatency(string iotHubName, string endpointName, st errorString = string.Empty; return true; } + public bool LogE2EEventProcessingLatency(string iotHubName, string endpointName, string endpointType, string status, long latencyInMs, out string errorString) { errorString = string.Empty; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryI18NTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryI18NTest.cs index 88e7fd5aefb..028f019a979 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryI18NTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryI18NTest.cs @@ -4,9 +4,9 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System; using System.Collections.Generic; using System.Text; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; public class BodyQueryI18NTest : RoutingUnitTestBase @@ -26,16 +26,18 @@ public class BodyQueryI18NTest : RoutingUnitTestBase ""es"": ""Estoy usando %1"" }}"; - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, + static readonly IMessage Message1 = new Message( + TelemetryMessageSource.Instance, Encoding.UTF8.GetBytes(MessageBody), new Dictionary(), new Dictionary() { - { SystemProperties.ContentEncoding, "UTF-8" }, - { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, + { SystemProperties.ContentEncoding, "UTF-8" }, + { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.Welcome!.he = 'ברוכים הבאים!'")] [InlineData("$body.I'mnotusingspaces%1.es = 'Estoy usando %1'")] public void BodyQueryI18N_Success(string condition) @@ -45,7 +47,8 @@ public void BodyQueryI18N_Success(string condition) Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.Welcome!.I'mnotusingspaces%1.es = 'Estoy usando %1'")] public void BodyQueryI18N_Failure(string condition) { @@ -54,7 +57,8 @@ public void BodyQueryI18N_Failure(string condition) Assert.Equal(rule(Message1), Bool.False); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.Welcome!['I'm using spaces %1'] = 'ברוכים הבאים!'")] public void BodyQueryI18N_RouteCompilation(string condition) { diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryTest.cs index e35ff264b98..3a6b55afd85 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/BodyQueryTest.cs @@ -4,14 +4,12 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System; using System.Collections.Generic; using System.Text; - //using Microsoft.Azure.Devices.Common.Api; - //using Microsoft.Azure.Devices.DeviceManagement.Model; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; - public class BodyQueryTest: RoutingUnitTestBase + public class BodyQueryTest : RoutingUnitTestBase { const string MessageBody = @"{ @@ -44,7 +42,8 @@ public class BodyQueryTest: RoutingUnitTestBase } }"; - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, + static readonly IMessage Message1 = new Message( + TelemetryMessageSource.Instance, Encoding.UTF8.GetBytes(MessageBody), new Dictionary { @@ -57,16 +56,17 @@ public class BodyQueryTest: RoutingUnitTestBase { { "State", "CA" }, { "$body.message.Weather.Location.State", "CA" }, - { SystemProperties.ContentEncoding, "UTF-8" }, - { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, + { SystemProperties.ContentEncoding, "UTF-8" }, + { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - [Theory, Unit] + [Theory] + [Unit] // TODO = These tests don't pass at the moment, need to fix them. Might have to look into fixing the grammar. // Note - looks like Antlr code has changed internally from the version used in IoTHub codebase // which might affect the behavior. - //[InlineData("$body.properties,reported")] - //[InlineData("$body.properties []")] + // [InlineData("$body.properties,reported")] + // [InlineData("$body.properties []")] [InlineData("$body.properties[]")] [InlineData("$body.properties[1:2]")] [InlineData("$body;@")] @@ -76,7 +76,8 @@ public void BodyQuery_RouteCompilation(string condition) Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery)); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("50 >= $body.message.Weather.Temperature")] [InlineData("$BODY.message.Weather.Temperature >= 50")] [InlineData("$bODy.message.Weather.Temperature <= 50")] @@ -98,7 +99,8 @@ public void BodyQuery_Double_Success(string condition) Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("10 >= $body.message.Weather.Temperature")] [InlineData("$body.message.Weather.Temperature < 50")] [InlineData("$body.message.Weather.Temperature = '50'")] // no implicit cross type conversion @@ -118,7 +120,8 @@ public void BodyQuery_Double_Failure(string condition) Assert.Equal(rule(Message1), Bool.False); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.Temperature <> '100'")] [InlineData("$body.message.Weather.Location.City = City")] [InlineData("$body.message.Weather.Location.State = $State")] @@ -130,7 +133,8 @@ public void BodyQuery_String_Success(string condition) Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.Temperature ='100'")] [InlineData("$body.message.Weather.Temperature ='150'")] [InlineData("$body.message.Weather.Location.City != City")] @@ -144,7 +148,8 @@ public void BodyQuery_String_Failure(string condition) Assert.Equal(rule(Message1), Bool.False); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.IsEnabled")] [InlineData("$body.message.Weather.IsEnabled = true")] [InlineData("$body.message.Weather.IsDisabled = false")] @@ -158,7 +163,8 @@ public void BodyQuery_Bool(string condition) Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.IsEnabled AND $body.message.Weather.IsEnabled")] [InlineData("$body.message.Weather.IsEnabled OR $body.message.Weather.IsDisabled")] [InlineData("$body.message.Weather.IsDisabled OR NOT($body.message.Weather.IsDisabled)")] @@ -169,7 +175,8 @@ public void BodyQuery_Logical(string condition) Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body")] [InlineData("$body.message.Weather.HistoricalData[0].Temperature.InvalidKey")] [InlineData("$City <> $body.message.Weather.InvalidKey")] @@ -180,7 +187,8 @@ public void BodyQuery_Undefined(string condition) Assert.Equal(rule(Message1), Bool.Undefined); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.Temperature_PropertyConflict = '100'")] [InlineData("as_number($body.message.Weather.Temperature_PropertyConflict) = 100")] [InlineData("{$body.message.Weather.Temperature_PropertyConflict} <> 100")] @@ -195,7 +203,8 @@ public void BodyQuery_SysPropertyConflict(string condition) Assert.Equal(result, Bool.True); } - [Fact, Unit] + [Fact] + [Unit] public void BodyQuery_NotSupported() { string condition = "$BODY.message.Weather.Temperature >= 50"; @@ -204,7 +213,8 @@ public void BodyQuery_NotSupported() Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.None)); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.Time <> '100'")] [InlineData("$body.message.Weather <> null")] public void BodyQuery_NotSupportedJTokenTime(string condition) @@ -216,7 +226,8 @@ public void BodyQuery_NotSupportedJTokenTime(string condition) Assert.Equal(result, Bool.Undefined); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("$body.message.Weather.NullValue = null")] [InlineData("$body.message.Temperature = null")] [InlineData("$body.message.Weather.Temperature != null")] @@ -232,7 +243,8 @@ public void BodyQuery_Null(string condition) Assert.Equal(result, Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("abs($body.message.Weather.FreezingTemperature) = 50.4")] [InlineData("ceiling(as_number($body.message.Weather.PreciseTemperature)) = 51")] [InlineData("concat($body.message.Weather.Location.Street, ', ', $body.message.Weather.Location.City, ', ', $body.message.Weather.Location.State) = 'One Microsoft Way, Redmond, WA'")] @@ -263,7 +275,8 @@ public void BodyQuery_Builtins(string condition) Assert.Equal(result, Bool.True); } - [Fact, Unit] + [Fact] + [Unit] public void DebugBodyQuery() { string condition = "$BODY.State[0] != '40'"; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionCompilerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionCompilerTest.cs index 29e056d57b1..aaaef6c71cd 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionCompilerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionCompilerTest.cs @@ -4,846 +4,851 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; [ExcludeFromCodeCoverage] public class ConditionCompilerTest : RoutingUnitTestBase { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[0], - new Dictionary { { "key1", "value1" }, { "key2", "3" }, { "key3", "VALUE3"}, { "null_value", null }, { "$sys4", "value4" } }, - new Dictionary { { "sys1", "sysvalue1" }, { "sys2", "4" }, { "sys3", "SYSVALUE3"}, { "sysnull", null}, { "sys4", "sysvalue4" } }); + static readonly IMessage Message1 = new Message( + TelemetryMessageSource.Instance, + new byte[0], + new Dictionary { { "key1", "value1" }, { "key2", "3" }, { "key3", "VALUE3" }, { "null_value", null }, { "$sys4", "value4" } }, + new Dictionary { { "sys1", "sysvalue1" }, { "sys2", "4" }, { "sys3", "SYSVALUE3" }, { "sysnull", null }, { "sys4", "sysvalue4" } }); + + [Theory] + [Unit] + [MemberData(nameof(TestSuccessDataSource.TestData), MemberType = typeof(TestSuccessDataSource))] + public void TestSuccess(string condition, Bool expected) + { + var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route); + Assert.Equal(expected, rule(Message1)); + } - static class TestSuccessDataSource + [Fact] + [Unit] + public void TestTest() { - public static IEnumerable TestData => Data; + string condition = "as_number(key2) = 3"; + Bool expected = Bool.True; - static readonly IList Data = new List + var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route); + Assert.Equal(expected, rule(Message1)); + } + + [Fact] + [Unit] + public void TestSubstringLimits() + { + for (int i = -10; i <= 10; i++) { - // Empty - new object[] { "", Bool.Undefined }, - new object[] { " ", Bool.Undefined }, + string condition = $"substring('hello', {i}) = 'he'"; + var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); - // Literals - new object[] { "3", Bool.Undefined }, - new object[] { "\"a string\"", Bool.Undefined }, - new object[] { "'a string'", Bool.Undefined }, + // assert doesn't throw + Func rule = RouteCompiler.Instance.Compile(route); + rule(Message1); + } - // Equals - new object[] { "3 = 3", Bool.True }, - new object[] { "3 = 4", Bool.False }, - new object[] { "\"3\" = \"3\"", Bool.True }, - new object[] { "\"3\" = \"4\"", Bool.False }, - new object[] { "'3' = '3'", Bool.True }, - new object[] { "'3' = '4'", Bool.False }, - new object[] { "true = true", Bool.True }, - new object[] { "true = false", Bool.False }, - new object[] { "false = false", Bool.True }, - new object[] { "false = true", Bool.False }, - new object[] { "key2 = \"3\"", Bool.True }, - new object[] { "key2 = '3'", Bool.True }, - new object[] { "as_number(key2) = 3", Bool.True }, - new object[] { "as_number(key2) = 4", Bool.False }, - new object[] { "is_defined(none = \"4\")", Bool.False }, - new object[] { "as_number(none) = 3", Bool.Undefined }, - new object[] { "$sys2 = \"4\"", Bool.True }, - new object[] { "$sys2 = '4'", Bool.True }, - new object[] { "as_number($sys2) = 4", Bool.True }, - new object[] { "as_number($sys2) = 3", Bool.False }, - new object[] { "is_defined($none)", Bool.False }, - new object[] { "as_number($none) = 3", Bool.Undefined }, + for (int i = -10; i <= 10; i++) + { + for (int j = -10; j <= 10; j++) + { + string condition = $"substring('hello', {i}, {j}) = 'he'"; + var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); - // Not equals - new object[] { "3 != 3", Bool.False }, - new object[] { "3 != 4", Bool.True }, - new object[] { "\"3\" != \"3\"", Bool.False }, - new object[] { "\"3\" != \"4\"", Bool.True }, - new object[] { "'3' != '3'", Bool.False }, - new object[] { "'3' != '4'", Bool.True }, - new object[] { "true != true", Bool.False }, - new object[] { "true != false", Bool.True }, - new object[] { "false != false", Bool.False }, - new object[] { "false != true", Bool.True }, - new object[] { "key2 != \"3\"", Bool.False }, - new object[] { "key2 != '3'", Bool.False }, - new object[] { "as_number(key2) != 3", Bool.False }, - new object[] { "as_number(key2) != 4", Bool.True }, - new object[] { "none != \"4\"", Bool.Undefined }, - new object[] { "as_number(none) != 3", Bool.Undefined }, - new object[] { "3 <> 3", Bool.False }, - new object[] { "3 <> 4", Bool.True }, - new object[] { "\"3\" <> \"3\"", Bool.False }, - new object[] { "\"3\" <> \"4\"", Bool.True }, - new object[] { "'3' <> '3'", Bool.False }, - new object[] { "'3' <> '4'", Bool.True }, - new object[] { "true <> true", Bool.False }, - new object[] { "true <> false", Bool.True }, - new object[] { "false <> false", Bool.False }, - new object[] { "false <> true", Bool.True }, - new object[] { "key2 <> \"3\"", Bool.False }, - new object[] { "key2 <> '3'", Bool.False }, - new object[] { "as_number(key2) <> 3", Bool.False }, - new object[] { "as_number(key2) <> 4", Bool.True }, - new object[] { "none <> \"4\"", Bool.Undefined }, - new object[] { "as_number(none) <> 3", Bool.Undefined }, + // assert doesn't throw + Func rule = RouteCompiler.Instance.Compile(route); + rule(Message1); + } + } + } - // And, Or, Not - new object[] { "true", Bool.True }, - new object[] { "false", Bool.False }, - new object[] { "not true", Bool.False }, - new object[] { "not false", Bool.True }, - new object[] { "not (true and false)", Bool.True }, - new object[] { "true AND true", Bool.True }, - new object[] { "true AND not true", Bool.False }, - new object[] { "true and true", Bool.True }, - new object[] { "true and false", Bool.False }, - new object[] { "false and true", Bool.False }, - new object[] { "false and false", Bool.False }, - new object[] { "true OR true", Bool.True }, - new object[] { "true or true", Bool.True }, - new object[] { "true or false", Bool.True }, - new object[] { "false or true", Bool.True }, - new object[] { "false or false", Bool.False }, + [Theory] + [Unit] + [MemberData(nameof(TestCompilationFailureDataSource.TestData), MemberType = typeof(TestCompilationFailureDataSource))] + public void TestCompilationFailure(string condition) + { + var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); + Assert.Throws(() => RouteCompiler.Instance.Compile(route)); + } - // Undefined - new object[] { "undefined", Bool.Undefined }, - new object[] { "undefined or false", Bool.Undefined }, - new object[] { "undefined or true", Bool.True }, - new object[] { "true or undefined", Bool.True }, - new object[] { "false or undefined", Bool.Undefined }, - new object[] { "undefined or undefined", Bool.Undefined }, - new object[] { "true and undefined", Bool.Undefined }, - new object[] { "false and undefined", Bool.False }, - new object[] { "undefined and false", Bool.False }, - new object[] { "undefined and undefined", Bool.Undefined }, - new object[] { "undefined < undefined", Bool.Undefined }, + static class TestCompilationFailureDataSource + { + static readonly IList Data = new List + { + new object[] { "is_defined(3 + none)" }, + new object[] { "is_defined(3 + null_value)" }, + new object[] { "is_defined(true + 3)" }, + new object[] { "is_defined(3 + true)" }, + new object[] { "is_defined(3 + undefined)" }, + new object[] { "is_defined('a' + 'b')" }, + new object[] { "is_defined(undefined + 3)" }, + new object[] { "is_defined(undefined + undefined)" }, + new object[] { "is_defined(3 - none)" }, + new object[] { "is_defined(3 - null_value)" }, + new object[] { "is_defined(true - 3)" }, + new object[] { "is_defined(3 - true)" }, + new object[] { "is_defined(3 - undefined)" }, + new object[] { "is_defined('a' - 'b')" }, + new object[] { "is_defined(undefined - 3)" }, + new object[] { "is_defined(undefined - undefined)" }, + new object[] { "is_defined(3 * none)" }, + new object[] { "is_defined(3 * null_value)" }, + new object[] { "is_defined(true * 3)" }, + new object[] { "is_defined(3 * true)" }, + new object[] { "is_defined(3 * undefined)" }, + new object[] { "is_defined('a' * 'b')" }, + new object[] { "is_defined(undefined * 3)" }, + new object[] { "is_defined(undefined * undefined)" }, + new object[] { "is_defined(3 / none)" }, + new object[] { "is_defined(3 / null_value)" }, + new object[] { "is_defined(true / 3)" }, + new object[] { "is_defined(3 / true)" }, + new object[] { "is_defined(3 / undefined)" }, + new object[] { "is_defined('a' / 'b')" }, + new object[] { "is_defined(undefined / 3)" }, + new object[] { "is_defined(undefined / undefined)" }, + new object[] { "is_defined(3 % none)" }, + new object[] { "is_defined(3 % null_value)" }, + new object[] { "is_defined(true % 3)" }, + new object[] { "is_defined(3 % true)" }, + new object[] { "is_defined(3 % undefined)" }, + new object[] { "is_defined('a' % 'b')" }, + new object[] { "is_defined(undefined % 3)" }, + new object[] { "is_defined(undefined % undefined)" }, + new object[] { "is_defined(3 = none)" }, + new object[] { "is_defined(3 != none)" }, + new object[] { "is_defined(3 <> none)" }, + new object[] { "is_defined(3 > none)" }, + new object[] { "is_defined(3 >= none)" }, + new object[] { "is_defined(3 < none)" }, + new object[] { "is_defined(3 <= none)" }, + new object[] { "is_defined(3 <= none)" }, + new object[] { "is_defined(3 <= none.key1)" }, + new object[] { "is_defined(3 <= none.key1.key2)" }, + new object[] { "is_defined(3 = null_value)" }, + new object[] { "is_defined(3 != null_value)" }, + new object[] { "is_defined(3 <> null_value)" }, + new object[] { "is_defined(3 > null_value)" }, + new object[] { "is_defined(3 >= null_value)" }, + new object[] { "is_defined(3 < null_value)" }, + new object[] { "is_defined(3 <= null_value)" }, + new object[] { "is_defined(3 <= null_value)" }, + // TODO = These tests don't pass at the moment, need to fix them. Might have to look into fixing the grammar. + // Note - looks like Antlr code has changed internally from the version used in IoTHub codebase + // which might affect the behavior. + // new object[] { "3 $ 4" }, + // new object[] { "$ sys1 = 'sysvalue1'" }, - new object[] { "true or none = 'true'", Bool.True }, - new object[] { "false or none = 'true'", Bool.Undefined }, - new object[] { "none = 'true' or true", Bool.True }, - new object[] { "none = 'true' or false", Bool.Undefined }, - new object[] { "none = 'true' or none2 = 'true'", Bool.Undefined }, + // Coalesce + new object[] { "key1 ?? \"hello\" = \"value1\"" }, + new object[] { "(key1 ?? 12.34)" }, + new object[] { "(12.34 ?? key1)" }, + new object[] { "(as_number(key2) ?? key1)" }, + new object[] { "(key1 ?? null)" }, + new object[] { "(null && key1)" }, + new object[] { "(key1 ?? true)" }, + new object[] { "(true && key1)" }, - new object[] { "true and none = 'true'", Bool.Undefined }, - new object[] { "false and none = 'true'", Bool.False }, - new object[] { "none = 'true' and true", Bool.Undefined }, - new object[] { "none = 'true' and false", Bool.False }, - new object[] { "none = 'true' and none2 = 'true'", Bool.Undefined }, + // Concat + new object[] { "concat() = 'avalue1d'" }, + new object[] { "concat('a') = 'avalue1d'" }, + new object[] { "concat(3) = 'avalue1d'" }, + new object[] { "concat('a', null) = 'avalue1d'" }, + new object[] { "concat('a', undefined) = 'avalue1d'" }, + new object[] { "concat('a', true) = 'avalue1d'" }, + new object[] { "'a' || null = 'avalue1d'" }, + new object[] { "'a' || undefined = 'avalue1d'" }, + new object[] { "'a' || true = 'avalue1d'" }, - new object[] { "not (none = 'true')", Bool.Undefined }, + // Length + new object[] { "length(12.34) = 'hello'" }, + new object[] { "length(null) = 'hello'" }, + new object[] { "length(undefined) = 'value1'" }, + new object[] { "length(true) = 'value1'" }, + new object[] { "length(false) = 'value1'" }, - // Coalesce - new object[] { "undefined ?? undefined", Bool.Undefined }, - new object[] { "(key1 ?? \"hello\") = \"value1\"", Bool.True }, - new object[] { "(none ?? \"hello\") = \"hello\"", Bool.True }, - new object[] { "(none ?? none ?? \"hello\") = \"hello\"", Bool.True }, - new object[] { "(as_number(key2) ?? 12.34) = 3", Bool.True }, - new object[] { "(as_number(key1) ?? 12.34) = 12.34", Bool.True }, - new object[] { "(3 % 0 ?? 12.34) = 12.34", Bool.True }, - new object[] { "(none ?? none) = \"value\"", Bool.Undefined }, + // Lower + new object[] { "lower(12.34) = 'hello'" }, + new object[] { "lower(null) = 'hello'" }, + new object[] { "lower(undefined) = 'value1'" }, + new object[] { "lower(true) = 'value1'" }, + new object[] { "lower(false) = 'value1'" }, - // Null - new object[] { "null and null", Bool.False }, - new object[] { "(null and null) = null", Bool.True }, - new object[] { "(null and false) = null", Bool.True }, - new object[] { "(null and true) = null", Bool.True }, - new object[] { "null or true", Bool.True }, - new object[] { "null or false", Bool.False }, - new object[] { "(null or null) = null", Bool.True }, - new object[] { "null", Bool.False }, - new object[] { "not null", Bool.True }, + // Upper + new object[] { "upper(12.34) = 'hello'" }, + new object[] { "upper(null) = 'hello'" }, + new object[] { "upper(undefined) = 'value1'" }, + new object[] { "upper(true) = 'value1'" }, + new object[] { "upper(false) = 'value1'" }, - // Addition - new object[] { "3 + 4 = 7", Bool.True }, - new object[] { "(3 + as_number(key2)) = 6", Bool.True }, - new object[] { "is_defined(3 + as_number(none))", Bool.False }, - new object[] { "is_defined(3 + as_number(key1))", Bool.False }, + // substring + new object[] { "substring() = 'hello'" }, + new object[] { "substring('hello') = 'hello'" }, + new object[] { "substring('hello', 'a') = 'hello'" }, + new object[] { "substring('hello', 'a', 'b') = 'hello'" }, + new object[] { "substring(12.34) = 'hello'" }, + new object[] { "substring(null) = 'hello'" }, + new object[] { "substring(undefined) = 'value1'" }, + new object[] { "substring(true) = 'value1'" }, + new object[] { "substring(false) = 'value1'" }, - // Subtraction - new object[] { "3 - 4 = -1", Bool.True }, - new object[] { "(3 - as_number(key2)) = 0", Bool.True }, - new object[] { "is_defined(3 - as_number(none))", Bool.False }, - new object[] { "is_defined(3 - as_number(key1))", Bool.False }, + // index_of + new object[] { "index_of()" }, + new object[] { "index_of('hello')" }, + new object[] { "index_of('hello', 'a', 'b')" }, + new object[] { "index_of('hello', 12.34)" }, + new object[] { "index_of(12.34, 'hello')" }, + new object[] { "index_of(12.34, 12.34)" }, + new object[] { "index_of(null, 'hello')" }, + new object[] { "index_of('hello', null)" }, + new object[] { "index_of(null, null)" }, + new object[] { "index_of(undefined, 'hello')" }, + new object[] { "index_of('hello', undefined)" }, + new object[] { "index_of(undefined, undefined)" }, + new object[] { "index_of(true, 'hello')" }, + new object[] { "index_of('hello', true)" }, + new object[] { "index_of(true, true)" }, - // Multiplication - new object[] { "3 * 4 = 12", Bool.True }, - new object[] { "(3 * as_number(key2)) = 9", Bool.True }, - new object[] { "is_defined(3 * as_number(none))", Bool.False }, - new object[] { "is_defined(3 * as_number(key1))", Bool.False }, + // starts_with + new object[] { "starts_with()" }, + new object[] { "starts_with('hello')" }, + new object[] { "starts_with('hello', 'a', 'b')" }, + new object[] { "starts_with('hello', 12.34)" }, + new object[] { "starts_with(12.34, 'hello')" }, + new object[] { "starts_with(12.34, 12.34)" }, + new object[] { "starts_with(null, 'hello')" }, + new object[] { "starts_with('hello', null)" }, + new object[] { "starts_with(null, null)" }, + new object[] { "starts_with(undefined, 'hello')" }, + new object[] { "starts_with('hello', undefined)" }, + new object[] { "starts_with(undefined, undefined)" }, + new object[] { "starts_with(true, 'hello')" }, + new object[] { "starts_with('hello', true)" }, + new object[] { "starts_with(true, true)" }, - // Division - new object[] { "3 / 4 = 0.75", Bool.True }, - new object[] { "(3 / as_number(key2)) = 1", Bool.True }, - new object[] { "is_defined(3 / as_number(none))", Bool.False }, - new object[] { "is_defined(3 / as_number(key1))", Bool.False }, + // ends_with + new object[] { "ends_with()" }, + new object[] { "ends_with('hello')" }, + new object[] { "ends_with('hello', 'a', 'b')" }, + new object[] { "ends_with('hello', 12.34)" }, + new object[] { "ends_with(12.34, 'hello')" }, + new object[] { "ends_with(12.34, 12.34)" }, + new object[] { "ends_with(null, 'hello')" }, + new object[] { "ends_with('hello', null)" }, + new object[] { "ends_with(null, null)" }, + new object[] { "ends_with(undefined, 'hello')" }, + new object[] { "ends_with('hello', undefined)" }, + new object[] { "ends_with(undefined, undefined)" }, + new object[] { "ends_with(true, 'hello')" }, + new object[] { "ends_with('hello', true)" }, + new object[] { "ends_with(true, true)" }, - // Modulo - new object[] { "4 % 3 = 1", Bool.True }, - new object[] { "(3 % as_number(key2)) = 0", Bool.True }, - new object[] { "is_defined(3 % 0)", Bool.False }, - new object[] { "is_defined(3 % as_number(none))", Bool.False }, - new object[] { "is_defined(3 % as_number(key1))", Bool.False }, + // contains + new object[] { "contains()" }, + new object[] { "contains('hello')" }, + new object[] { "contains('hello', 'a', 'b')" }, + new object[] { "contains('hello', 12.34)" }, + new object[] { "contains(12.34, 'hello')" }, + new object[] { "contains(12.34, 12.34)" }, + new object[] { "contains(null, 'hello')" }, + new object[] { "contains('hello', null)" }, + new object[] { "contains(null, null)" }, + new object[] { "contains(undefined, 'hello')" }, + new object[] { "contains('hello', undefined)" }, + new object[] { "contains(undefined, undefined)" }, + new object[] { "contains(true, 'hello')" }, + new object[] { "contains('hello', true)" }, + new object[] { "contains(true, true)" }, - // Negation - new object[] { "-1 < 0", Bool.True }, - new object[] { "-(3 + 4) = -7", Bool.True }, - new object[] { "is_defined(-undefined)", Bool.False }, - new object[] { "is_defined(-undefined)", Bool.False }, + // Abs + new object[] { "is_defined(abs(null))" }, + new object[] { "is_defined(abs(undefined))" }, + new object[] { "is_defined(abs(true))" }, + new object[] { "is_defined(abs(false))" }, + new object[] { "is_defined(abs(key1))" }, + new object[] { "is_defined(abs(3, 4))" }, - // Less than - new object[] { "4 < 7", Bool.True }, - new object[] { "7 < 7", Bool.False }, - new object[] { "8 < 7", Bool.False }, - new object[] { "3 + 4 < 7", Bool.False }, - new object[] { "3 < as_number(none)", Bool.Undefined }, - new object[] { "3 < as_number(null_value)", Bool.Undefined }, - new object[] { "'a' < 'b'", Bool.True }, - new object[] { "'a' < 'a'", Bool.False }, - new object[] { "'b' < 'a'", Bool.False }, - new object[] { "'a' < none", Bool.Undefined }, + // Exp + new object[] { "is_defined(exp(null))" }, + new object[] { "is_defined(exp(undefined))" }, + new object[] { "is_defined(exp(true))" }, + new object[] { "is_defined(exp(false))" }, + new object[] { "is_defined(exp(key1))" }, + new object[] { "is_defined(exp(3, 4))" }, - // Less than or equal - new object[] { "4 <= 7", Bool.True }, - new object[] { "7 <= 7", Bool.True }, - new object[] { "8 <= 7", Bool.False }, - new object[] { "3 + 4 <= 7", Bool.True }, - new object[] { "3 <= as_number(none)", Bool.Undefined }, - new object[] { "3 <= as_number(null_value)", Bool.Undefined }, - new object[] { "'a' <= 'b'", Bool.True }, - new object[] { "'a' <= 'a'", Bool.True }, - new object[] { "'b' <= 'a'", Bool.False }, - new object[] { "'a' <= none", Bool.Undefined }, + // Power + new object[] { "is_defined(power(null, 2))" }, + new object[] { "is_defined(power(2, null))" }, + new object[] { "is_defined(power(null, null))" }, + new object[] { "is_defined(power(undefined, 2))" }, + new object[] { "is_defined(power(2, undefined))" }, + new object[] { "is_defined(power(undefined, undefined))" }, + new object[] { "is_defined(power(true, 2))" }, + new object[] { "is_defined(power(2, true))" }, + new object[] { "is_defined(power(true, true))" }, + new object[] { "is_defined(power(key1, 2))" }, + new object[] { "is_defined(power(2, key1))" }, + new object[] { "is_defined(power(2, 3, 4))" }, - // Greater than - new object[] { "4 > 7", Bool.False }, - new object[] { "7 > 7", Bool.False }, - new object[] { "8 > 7", Bool.True }, - new object[] { "3 + 4 > 7", Bool.False }, - new object[] { "3 > as_number(none)", Bool.Undefined }, - new object[] { "3 > as_number(null_value)", Bool.Undefined }, - new object[] { "'a' > 'b'", Bool.False }, - new object[] { "'a' > 'a'", Bool.False }, - new object[] { "'b' > 'a'", Bool.True }, - new object[] { "'a' > none", Bool.Undefined }, + // Square + new object[] { "is_defined(square(null))" }, + new object[] { "is_defined(square(undefined))" }, + new object[] { "is_defined(square(true))" }, + new object[] { "is_defined(square(false))" }, + new object[] { "is_defined(square(key1))" }, + new object[] { "is_defined(square(3, 3))" }, - // Greater than or equal - new object[] { "4 >= 7", Bool.False }, - new object[] { "7 >= 7", Bool.True }, - new object[] { "8 >= 7", Bool.True }, - new object[] { "3 + 4 >= 7", Bool.True }, - new object[] { "3 >= as_number(none)", Bool.Undefined }, - new object[] { "3 >= as_number(null_value)", Bool.Undefined }, - new object[] { "'a' >= 'b'", Bool.False }, - new object[] { "'a' >= 'a'", Bool.True }, - new object[] { "'b' >= 'a'", Bool.True }, - new object[] { "'a' > none", Bool.Undefined }, + // Ceiling + new object[] { "is_defined(ceiling(null))" }, + new object[] { "is_defined(ceiling(undefined))" }, + new object[] { "is_defined(ceiling(true))" }, + new object[] { "is_defined(ceiling(false))" }, + new object[] { "is_defined(ceiling(key1))" }, + new object[] { "is_defined(ceiling(3, 3))" }, - // Access - new object[] { "key1 = \"value1\"", Bool.True }, - new object[] { "KEY1 = \"value1\"", Bool.True }, - new object[] { "key1 = \"value1\" and key1 = \"value1\"", Bool.True }, - new object[] { "key1 = \"value2\" or key1 = \"value1\"", Bool.True }, - new object[] { "key1 = \"value2\"", Bool.False }, - new object[] { "key2 = \"value1\"", Bool.False }, - new object[] { "none = \"value1\"", Bool.Undefined }, - new object[] { "$sys4 = \"value4\"", Bool.True }, - new object[] { "{$sys4} = \"sysvalue4\"", Bool.True }, + // Floor + new object[] { "is_defined(floor(null))" }, + new object[] { "is_defined(floor(undefined))" }, + new object[] { "is_defined(floor(true))" }, + new object[] { "is_defined(floor(false))" }, + new object[] { "is_defined(floor(key1))" }, + new object[] { "is_defined(floor(3, 3))" }, - // system properties - new object[] { "$sys1 = \"sysvalue1\"", Bool.True }, - new object[] { "{$sys1} = \"sysvalue1\"", Bool.True }, - new object[] { "{ $sys1 } = \"sysvalue1\"", Bool.True }, + // Sign + new object[] { "is_defined(sign(null))" }, + new object[] { "is_defined(sign(undefined))" }, + new object[] { "is_defined(sign(true))" }, + new object[] { "is_defined(sign(false))" }, + new object[] { "is_defined(sign(key1))" }, + new object[] { "is_defined(sign(3, 3))" }, - // as_number - new object[] { "as_number(\"3\") = 3", Bool.True }, - new object[] { "AS_NUMBER(\"3\") = 3", Bool.True }, - new object[] { "AS_NUMBER('10e10') = 10e10", Bool.True }, - new object[] { "AS_NUMBER('10e1000') = 1/0", Bool.Undefined }, - new object[] { "AS_NUMBER('10000000000000000000000000') = 1e25", Bool.True }, - new object[] { "as_number(key2) = 3", Bool.True }, - new object[] { "as_number(key1) = 3", Bool.Undefined }, - new object[] { "as_number(key3) = 3", Bool.Undefined }, - new object[] { "as_number(key2) * 3 = 9", Bool.True }, - new object[] { "as_number(key3) * 3 = 9", Bool.Undefined }, - new object[] { "as_number(none) * 3 = 9", Bool.Undefined }, - new object[] { "as_number(null_value) * 3 = 9", Bool.Undefined }, - new object[] { "as_number(as_number(4)) * 3 = 12", Bool.True }, + // Sqrt + new object[] { "is_defined(sqrt(null))" }, + new object[] { "is_defined(sqrt(undefined))" }, + new object[] { "is_defined(sqrt(true))" }, + new object[] { "is_defined(sqrt(false))" }, + new object[] { "is_defined(sqrt(key1))" }, + new object[] { "is_defined(sqrt(3, 3))" }, - // is_bool - new object[] { "is_bool(12.34)", Bool.False }, - new object[] { "is_bool('not a number')", Bool.False }, - new object[] { "is_bool(\"not a number\")", Bool.False }, - new object[] { "is_bool(true)", Bool.True }, - new object[] { "IS_BOOL(true)", Bool.True }, - new object[] { "is_bool(false)", Bool.True }, - new object[] { "is_bool(none)", Bool.False }, - new object[] { "is_bool(key1)", Bool.False }, + new object[] { "test == true" }, + new object[] { "true == test" }, - // is_defined - new object[] { "is_defined(undefined)", Bool.False }, - new object[] { "IS_DEFINED(undefined)", Bool.False }, - new object[] { "is_defined(key1)", Bool.True }, - new object[] { "is_defined(key4)", Bool.False }, - new object[] { "is_defined(3 * as_number(key4))", Bool.False }, - new object[] { "is_defined(undefined and true)", Bool.False }, - new object[] { "is_defined(as_number(\"3\"))", Bool.True }, - new object[] { "is_defined(as_number(\"apple\"))", Bool.False }, - new object[] { "is_defined(null_value)", Bool.True }, - new object[] { "is_defined(null)", Bool.True }, - new object[] { "is_defined(10e1000)", Bool.False }, + // empty unterminated string + new object[] { "none = \"" }, - // is_null - new object[] { "is_null(12.34)", Bool.False }, - new object[] { "is_null('not a number')", Bool.False }, - new object[] { "is_null(\"not a number\")", Bool.False }, - new object[] { "is_null(true)", Bool.False }, - new object[] { "is_null(false)", Bool.False }, - new object[] { "is_null(3 / 0)", Bool.False }, - new object[] { "is_null(as_number(key2))", Bool.False }, - new object[] { "is_null(as_number(key1))", Bool.False }, - new object[] { "is_null(as_number(key1) / as_number(key2))", Bool.False }, - new object[] { "is_null(null)", Bool.True }, - new object[] { "IS_NULL(null)", Bool.True }, - new object[] { "is_null(null_value)", Bool.True }, - new object[] { "is_null(none)", Bool.False }, + // and + new object[] { "none and true" }, + new object[] { "undefined and \"hello\"" }, - // is_number - new object[] { "is_number(12.34)", Bool.True }, - new object[] { "IS_NUMBER(12.34)", Bool.True }, - new object[] { "is_number('not a number')", Bool.False }, - new object[] { "is_number(\"not a number\")", Bool.False }, - new object[] { "is_number(true)", Bool.False }, - new object[] { "is_number(false)", Bool.False }, - new object[] { "is_number(3 / 0)", Bool.False }, - new object[] { "is_number(as_number(key2))", Bool.True }, - new object[] { "is_number(as_number(key1))", Bool.False }, - new object[] { "is_number(as_number(key1) / as_number(key2))", Bool.False }, + // or + new object[] { "none or true" }, - // is_string - new object[] { "is_string(12.34)", Bool.False }, - new object[] { "is_string('not a number')", Bool.True }, - new object[] { "IS_STRING('not a number')", Bool.True }, - new object[] { "is_string(\"not a number\")", Bool.True }, - new object[] { "is_string(true)", Bool.False }, - new object[] { "is_string(false)", Bool.False }, - new object[] { "is_string(none)", Bool.False }, - new object[] { "is_string(key1)", Bool.True }, - new object[] { "is_string(null_value)", Bool.False }, - new object[] { "is_string('a' || 'b')", Bool.True }, + // negate + new object[] { "- (key1)" }, - // concat - new object[] { "concat('a', 'b', 'c', 'd') = 'abcd'", Bool.True }, - new object[] { "CONCAT('a', 'b', 'c', 'd') = 'abcd'", Bool.True }, - new object[] { "concat('a', 'b') = concat('a', 'b')", Bool.True }, - new object[] { "concat('a', key1, 'd') = 'avalue1d'", Bool.True }, - new object[] { "is_defined(concat('a', none, 'd'))", Bool.False }, - new object[] { "'a' || 'b' || 'c' || 'd' = 'abcd'", Bool.True }, - new object[] { "'a' || key1 || 'd' = 'avalue1d'", Bool.True }, - new object[] { "'a' || null_value || 'd' = 'ad'", Bool.True }, - new object[] { "concat('a', null_value, 'd') = 'ad'", Bool.True }, - new object[] { "is_defined('a' || none || 'd')", Bool.False }, - new object[] { "is_defined('a' || null_value || 'd')", Bool.True }, + // not + new object[] { "is_defined(not key1)" }, + }; - // length - new object[] { "length('HELLO') = 5", Bool.True }, - new object[] { "LENGTH('') = 0", Bool.True }, - new object[] { "length(key3) = 6", Bool.True }, - new object[] { "is_defined(length(null_value))", Bool.False }, - new object[] { "is_defined(length(none))", Bool.False }, + public static IEnumerable TestData => Data; + } - // lower - new object[] { "lower('HELLO') = 'hello'", Bool.True }, - new object[] { "LOWER('hello') = 'hello'", Bool.True }, - new object[] { "lower(key3) = 'value3'", Bool.True }, - new object[] { "is_defined(lower(null_value))", Bool.False }, - new object[] { "is_defined(lower(none))", Bool.False }, + static class TestSuccessDataSource + { + static readonly IList Data = new List + { + // Empty + new object[] { string.Empty, Bool.Undefined }, + new object[] { " ", Bool.Undefined }, - // upper - new object[] { "upper('HELLO') = 'HELLO'", Bool.True }, - new object[] { "UPPER('hello') = 'HELLO'", Bool.True }, - new object[] { "upper(key1) = 'VALUE1'", Bool.True }, - new object[] { "is_defined(upper(null_value))", Bool.False }, - new object[] { "is_defined(upper(none))", Bool.False }, + // Literals + new object[] { "3", Bool.Undefined }, + new object[] { "\"a string\"", Bool.Undefined }, + new object[] { "'a string'", Bool.Undefined }, - // substring - new object[] { "substring('abc', 1) = 'bc'", Bool.True }, - new object[] { "substring('abc', 2) = 'c'", Bool.True }, - new object[] { "SUBSTRING('abc', 3) = 'c'", Bool.False }, - new object[] { "SUBSTRING('abc', 4) = 'c'", Bool.Undefined }, - new object[] { "substring(key1, 1) = 'alue1'", Bool.True }, - new object[] { "substring('abc', -1) = ''", Bool.Undefined }, - new object[] { "substring('abc', 1 % 0) = ''", Bool.Undefined }, - new object[] { "substring(null_value, 1) = 'a'", Bool.Undefined }, - new object[] { "substring(none, 1) = 'a'", Bool.Undefined }, + // Equals + new object[] { "3 = 3", Bool.True }, + new object[] { "3 = 4", Bool.False }, + new object[] { "\"3\" = \"3\"", Bool.True }, + new object[] { "\"3\" = \"4\"", Bool.False }, + new object[] { "'3' = '3'", Bool.True }, + new object[] { "'3' = '4'", Bool.False }, + new object[] { "true = true", Bool.True }, + new object[] { "true = false", Bool.False }, + new object[] { "false = false", Bool.True }, + new object[] { "false = true", Bool.False }, + new object[] { "key2 = \"3\"", Bool.True }, + new object[] { "key2 = '3'", Bool.True }, + new object[] { "as_number(key2) = 3", Bool.True }, + new object[] { "as_number(key2) = 4", Bool.False }, + new object[] { "is_defined(none = \"4\")", Bool.False }, + new object[] { "as_number(none) = 3", Bool.Undefined }, + new object[] { "$sys2 = \"4\"", Bool.True }, + new object[] { "$sys2 = '4'", Bool.True }, + new object[] { "as_number($sys2) = 4", Bool.True }, + new object[] { "as_number($sys2) = 3", Bool.False }, + new object[] { "is_defined($none)", Bool.False }, + new object[] { "as_number($none) = 3", Bool.Undefined }, - new object[] { "substring('abc', 1, 1) = 'b'", Bool.True }, - new object[] { "substring('abc', 1, 0) = ''", Bool.True }, - new object[] { "SUBSTRING(key1, 1, 1) = 'a'", Bool.True }, - new object[] { "substring('abc', -1, 0) = ''", Bool.Undefined }, - new object[] { "substring('abc', 1, -2) = ''", Bool.Undefined }, - new object[] { "substring('abc', 1 % 0, 1) = ''", Bool.Undefined }, - new object[] { "substring('abc', 1, 1 % 0) = ''", Bool.Undefined }, - new object[] { "substring(null_value, 1, 1) = 'a'", Bool.Undefined }, - new object[] { "substring(none, 1, 1) = 'a'", Bool.Undefined }, + // Not equals + new object[] { "3 != 3", Bool.False }, + new object[] { "3 != 4", Bool.True }, + new object[] { "\"3\" != \"3\"", Bool.False }, + new object[] { "\"3\" != \"4\"", Bool.True }, + new object[] { "'3' != '3'", Bool.False }, + new object[] { "'3' != '4'", Bool.True }, + new object[] { "true != true", Bool.False }, + new object[] { "true != false", Bool.True }, + new object[] { "false != false", Bool.False }, + new object[] { "false != true", Bool.True }, + new object[] { "key2 != \"3\"", Bool.False }, + new object[] { "key2 != '3'", Bool.False }, + new object[] { "as_number(key2) != 3", Bool.False }, + new object[] { "as_number(key2) != 4", Bool.True }, + new object[] { "none != \"4\"", Bool.Undefined }, + new object[] { "as_number(none) != 3", Bool.Undefined }, + new object[] { "3 <> 3", Bool.False }, + new object[] { "3 <> 4", Bool.True }, + new object[] { "\"3\" <> \"3\"", Bool.False }, + new object[] { "\"3\" <> \"4\"", Bool.True }, + new object[] { "'3' <> '3'", Bool.False }, + new object[] { "'3' <> '4'", Bool.True }, + new object[] { "true <> true", Bool.False }, + new object[] { "true <> false", Bool.True }, + new object[] { "false <> false", Bool.False }, + new object[] { "false <> true", Bool.True }, + new object[] { "key2 <> \"3\"", Bool.False }, + new object[] { "key2 <> '3'", Bool.False }, + new object[] { "as_number(key2) <> 3", Bool.False }, + new object[] { "as_number(key2) <> 4", Bool.True }, + new object[] { "none <> \"4\"", Bool.Undefined }, + new object[] { "as_number(none) <> 3", Bool.Undefined }, - new object[] { "substring('hello', 0, 5) = 'hello'", Bool.True }, - new object[] { "substring('start', 0, 4) = 'star'", Bool.True }, - new object[] { "substring('hello', 1, 4) = 'ello'", Bool.True }, - new object[] { "substring('hello', 4, 1) = 'o'", Bool.True }, - new object[] { "substring('hello', 4, 0) = ''", Bool.True }, + // And, Or, Not + new object[] { "true", Bool.True }, + new object[] { "false", Bool.False }, + new object[] { "not true", Bool.False }, + new object[] { "not false", Bool.True }, + new object[] { "not (true and false)", Bool.True }, + new object[] { "true AND true", Bool.True }, + new object[] { "true AND not true", Bool.False }, + new object[] { "true and true", Bool.True }, + new object[] { "true and false", Bool.False }, + new object[] { "false and true", Bool.False }, + new object[] { "false and false", Bool.False }, + new object[] { "true OR true", Bool.True }, + new object[] { "true or true", Bool.True }, + new object[] { "true or false", Bool.True }, + new object[] { "false or true", Bool.True }, + new object[] { "false or false", Bool.False }, - // index_of - new object[] { "index_of('abcdef', 'cd') = 2", Bool.True }, - new object[] { "index_of('abc', 'xz') = -1", Bool.True }, - new object[] { "INDEX_OF(key1, 'val') = 0", Bool.True }, - new object[] { "index_of('lue1value1', key1) = 4", Bool.True }, - new object[] { "index_of('abc', key1) = -1", Bool.True }, - new object[] { "index_of(null_value, '1') = 0", Bool.Undefined }, - new object[] { "index_of(none, '1') = 0", Bool.Undefined }, + // Undefined + new object[] { "undefined", Bool.Undefined }, + new object[] { "undefined or false", Bool.Undefined }, + new object[] { "undefined or true", Bool.True }, + new object[] { "true or undefined", Bool.True }, + new object[] { "false or undefined", Bool.Undefined }, + new object[] { "undefined or undefined", Bool.Undefined }, + new object[] { "true and undefined", Bool.Undefined }, + new object[] { "false and undefined", Bool.False }, + new object[] { "undefined and false", Bool.False }, + new object[] { "undefined and undefined", Bool.Undefined }, + new object[] { "undefined < undefined", Bool.Undefined }, - // starts_with - new object[] { "starts_with('abc', 'ab')", Bool.True }, - new object[] { "starts_with('abc', 'xz')", Bool.False }, - new object[] { "STARTS_WITH(key1, 'val')", Bool.True }, - new object[] { "starts_with('value1value1', key1)", Bool.True }, - new object[] { "starts_with('abc', key1)", Bool.False }, - new object[] { "starts_with(null_value, '1')", Bool.Undefined }, - new object[] { "starts_with(none, '1')", Bool.Undefined }, + new object[] { "true or none = 'true'", Bool.True }, + new object[] { "false or none = 'true'", Bool.Undefined }, + new object[] { "none = 'true' or true", Bool.True }, + new object[] { "none = 'true' or false", Bool.Undefined }, + new object[] { "none = 'true' or none2 = 'true'", Bool.Undefined }, - // ends_with - new object[] { "ends_with('abc', 'bc')", Bool.True }, - new object[] { "ends_with('abc', 'xz')", Bool.False }, - new object[] { "ENDS_WITH(key1, 'e1')", Bool.True }, - new object[] { "ends_with('value1value1', key1)", Bool.True }, - new object[] { "ends_with('abc', key1)", Bool.False }, - new object[] { "ends_with(null_value, '1')", Bool.Undefined }, - new object[] { "ends_with(none, '1')", Bool.Undefined }, + new object[] { "true and none = 'true'", Bool.Undefined }, + new object[] { "false and none = 'true'", Bool.False }, + new object[] { "none = 'true' and true", Bool.Undefined }, + new object[] { "none = 'true' and false", Bool.False }, + new object[] { "none = 'true' and none2 = 'true'", Bool.Undefined }, - // contains - new object[] { "contains('abc', 'ab')", Bool.True }, - new object[] { "contains('abc', 'xz')", Bool.False }, - new object[] { "CONTAINS(key1, 'val')", Bool.True }, - new object[] { "contains('value1value1', key1)", Bool.True }, - new object[] { "contains('abc', key1)", Bool.False }, - new object[] { "contains(null_value, '1')", Bool.Undefined }, - new object[] { "contains(none, '1')", Bool.Undefined }, - - // abs - new object[] { "abs(-12) = 12", Bool.True }, - new object[] { "abs(12) = 12", Bool.True }, - new object[] { "ABS(as_number(key2)) = 3", Bool.True }, - new object[] { "is_defined(abs(as_number(key1)))", Bool.False }, - new object[] { "is_defined(abs(3 / 0))", Bool.True }, - new object[] { "is_defined(abs(3 % 0))", Bool.False }, - - // exp - new object[] { "exp(0) = 1", Bool.True }, - new object[] { "abs(exp(1) - 2.718281) < 1e-6", Bool.True }, - new object[] { "abs(EXP(as_number(key2)) - 20.085536) < 1e-6", Bool.True }, - new object[] { "exp(4000) = 3/0", Bool.True }, - new object[] { "is_defined(exp(as_number(key1)))", Bool.False }, - new object[] { "is_defined(exp(3 / 0))", Bool.True }, - new object[] { "is_defined(exp(3 % 0))", Bool.False }, - - // power - new object[] { "power(2, 3) = 8", Bool.True }, - new object[] { "POWER(3, 2) = 9", Bool.True }, - new object[] { "POWER(10, 4000) = 3/0", Bool.True }, - new object[] { "power(as_number(key2), as_number(key2)) = 27", Bool.True }, - new object[] { "is_defined(power(as_number(key1), as_number(key1)))", Bool.False }, - new object[] { "is_defined(power(3 / 0, 2))", Bool.True }, - new object[] { "is_defined(power(2, 3 / 0))", Bool.True }, - new object[] { "is_defined(power(3 % 0, 2))", Bool.False }, - new object[] { "is_defined(power(2, 3 % 0))", Bool.False }, - - // square - new object[] { "square(2) = 4", Bool.True }, - new object[] { "SQUARE(3) = 9", Bool.True }, - new object[] { "square(as_number(key2)) = 9", Bool.True }, - new object[] { "is_defined(square(as_number(key1)))", Bool.False }, - new object[] { "is_defined(square(3 / 0))", Bool.True }, - new object[] { "is_defined(square(3 % 0))", Bool.False }, + new object[] { "not (none = 'true')", Bool.Undefined }, - // ceiling - new object[] { "ceiling(2) = 2", Bool.True }, - new object[] { "ceiling(2.1) = 3", Bool.True }, - new object[] { "ceiling(-2.1) = -2", Bool.True }, - new object[] { "CEILING(-2) = -2", Bool.True }, - new object[] { "ceiling(as_number(key2)) = 3", Bool.True }, - new object[] { "is_defined(ceiling(as_number(key1)))", Bool.False }, - new object[] { "is_defined(ceiling(3 / 0))", Bool.True }, - new object[] { "is_defined(ceiling(3 % 0))", Bool.False }, + // Coalesce + new object[] { "undefined ?? undefined", Bool.Undefined }, + new object[] { "(key1 ?? \"hello\") = \"value1\"", Bool.True }, + new object[] { "(none ?? \"hello\") = \"hello\"", Bool.True }, + new object[] { "(none ?? none ?? \"hello\") = \"hello\"", Bool.True }, + new object[] { "(as_number(key2) ?? 12.34) = 3", Bool.True }, + new object[] { "(as_number(key1) ?? 12.34) = 12.34", Bool.True }, + new object[] { "(3 % 0 ?? 12.34) = 12.34", Bool.True }, + new object[] { "(none ?? none) = \"value\"", Bool.Undefined }, - // floor - new object[] { "floor(2) = 2", Bool.True }, - new object[] { "floor(2.1) = 2", Bool.True }, - new object[] { "floor(-2.1) = -3", Bool.True }, - new object[] { "FLOOR(-2) = -2", Bool.True }, - new object[] { "floor(as_number(key2)) = 3", Bool.True }, - new object[] { "is_defined(floor(as_number(key1)))", Bool.False }, - new object[] { "is_defined(floor(3 / 0))", Bool.True }, - new object[] { "is_defined(floor(3 % 0))", Bool.False }, + // Null + new object[] { "null and null", Bool.False }, + new object[] { "(null and null) = null", Bool.True }, + new object[] { "(null and false) = null", Bool.True }, + new object[] { "(null and true) = null", Bool.True }, + new object[] { "null or true", Bool.True }, + new object[] { "null or false", Bool.False }, + new object[] { "(null or null) = null", Bool.True }, + new object[] { "null", Bool.False }, + new object[] { "not null", Bool.True }, - // sign - new object[] { "sign(0) = 0", Bool.True }, - new object[] { "sign(2) = 1", Bool.True }, - new object[] { "sign(2.1) = 1", Bool.True }, - new object[] { "sign(-2.1) = -1", Bool.True }, - new object[] { "SIGN(-2) = -1", Bool.True }, - new object[] { "sign(as_number(key2)) = 1", Bool.True }, - new object[] { "is_defined(sign(as_number(key1)))", Bool.False }, - new object[] { "sign(3 / 0) = 1", Bool.True }, - new object[] { "sign(-3 / 0) = -1", Bool.True }, - new object[] { "is_defined(sign(3 % 0))", Bool.False }, + // Addition + new object[] { "3 + 4 = 7", Bool.True }, + new object[] { "(3 + as_number(key2)) = 6", Bool.True }, + new object[] { "is_defined(3 + as_number(none))", Bool.False }, + new object[] { "is_defined(3 + as_number(key1))", Bool.False }, - // sqrt - new object[] { "sqrt(4) = 2", Bool.True }, - new object[] { "SQRT(-4) = -2", Bool.Undefined }, - new object[] { "abs(sqrt(as_number(key2)) - 1.732050) < 1e-6", Bool.True }, - new object[] { "is_defined(sqrt(as_number(key1)))", Bool.False }, - new object[] { "is_defined(sqrt(3 / 0))", Bool.True }, - new object[] { "is_defined(sqrt(3 % 0))", Bool.False }, - }; - } + // Subtraction + new object[] { "3 - 4 = -1", Bool.True }, + new object[] { "(3 - as_number(key2)) = 0", Bool.True }, + new object[] { "is_defined(3 - as_number(none))", Bool.False }, + new object[] { "is_defined(3 - as_number(key1))", Bool.False }, - [Theory, Unit] - [MemberData(nameof(TestSuccessDataSource.TestData), MemberType = typeof(TestSuccessDataSource))] - public void TestSuccess(string condition, Bool expected) - { - var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); - Func rule = RouteCompiler.Instance.Compile(route); - Assert.Equal(expected, rule(Message1)); - } + // Multiplication + new object[] { "3 * 4 = 12", Bool.True }, + new object[] { "(3 * as_number(key2)) = 9", Bool.True }, + new object[] { "is_defined(3 * as_number(none))", Bool.False }, + new object[] { "is_defined(3 * as_number(key1))", Bool.False }, - [Fact, Unit] - public void TestTest() - { - string condition = "as_number(key2) = 3"; - Bool expected = Bool.True; + // Division + new object[] { "3 / 4 = 0.75", Bool.True }, + new object[] { "(3 / as_number(key2)) = 1", Bool.True }, + new object[] { "is_defined(3 / as_number(none))", Bool.False }, + new object[] { "is_defined(3 / as_number(key1))", Bool.False }, - var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); - Func rule = RouteCompiler.Instance.Compile(route); - Assert.Equal(expected, rule(Message1)); - } + // Modulo + new object[] { "4 % 3 = 1", Bool.True }, + new object[] { "(3 % as_number(key2)) = 0", Bool.True }, + new object[] { "is_defined(3 % 0)", Bool.False }, + new object[] { "is_defined(3 % as_number(none))", Bool.False }, + new object[] { "is_defined(3 % as_number(key1))", Bool.False }, - [Fact, Unit] - public void TestSubstringLimits() - { - for (int i = -10; i <= 10; i++) - { - string condition = $"substring('hello', {i}) = 'he'"; - var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); + // Negation + new object[] { "-1 < 0", Bool.True }, + new object[] { "-(3 + 4) = -7", Bool.True }, + new object[] { "is_defined(-undefined)", Bool.False }, + new object[] { "is_defined(-undefined)", Bool.False }, - // assert doesn't throw - Func rule = RouteCompiler.Instance.Compile(route); - rule(Message1); - } + // Less than + new object[] { "4 < 7", Bool.True }, + new object[] { "7 < 7", Bool.False }, + new object[] { "8 < 7", Bool.False }, + new object[] { "3 + 4 < 7", Bool.False }, + new object[] { "3 < as_number(none)", Bool.Undefined }, + new object[] { "3 < as_number(null_value)", Bool.Undefined }, + new object[] { "'a' < 'b'", Bool.True }, + new object[] { "'a' < 'a'", Bool.False }, + new object[] { "'b' < 'a'", Bool.False }, + new object[] { "'a' < none", Bool.Undefined }, - for (int i = -10; i <= 10; i++) - { - for (int j = -10; j <= 10; j++) - { - string condition = $"substring('hello', {i}, {j}) = 'he'"; - var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); + // Less than or equal + new object[] { "4 <= 7", Bool.True }, + new object[] { "7 <= 7", Bool.True }, + new object[] { "8 <= 7", Bool.False }, + new object[] { "3 + 4 <= 7", Bool.True }, + new object[] { "3 <= as_number(none)", Bool.Undefined }, + new object[] { "3 <= as_number(null_value)", Bool.Undefined }, + new object[] { "'a' <= 'b'", Bool.True }, + new object[] { "'a' <= 'a'", Bool.True }, + new object[] { "'b' <= 'a'", Bool.False }, + new object[] { "'a' <= none", Bool.Undefined }, - // assert doesn't throw - Func rule = RouteCompiler.Instance.Compile(route); - rule(Message1); - } - } - } + // Greater than + new object[] { "4 > 7", Bool.False }, + new object[] { "7 > 7", Bool.False }, + new object[] { "8 > 7", Bool.True }, + new object[] { "3 + 4 > 7", Bool.False }, + new object[] { "3 > as_number(none)", Bool.Undefined }, + new object[] { "3 > as_number(null_value)", Bool.Undefined }, + new object[] { "'a' > 'b'", Bool.False }, + new object[] { "'a' > 'a'", Bool.False }, + new object[] { "'b' > 'a'", Bool.True }, + new object[] { "'a' > none", Bool.Undefined }, - static class TestCompilationFailureDataSource - { - public static IEnumerable TestData => Data; + // Greater than or equal + new object[] { "4 >= 7", Bool.False }, + new object[] { "7 >= 7", Bool.True }, + new object[] { "8 >= 7", Bool.True }, + new object[] { "3 + 4 >= 7", Bool.True }, + new object[] { "3 >= as_number(none)", Bool.Undefined }, + new object[] { "3 >= as_number(null_value)", Bool.Undefined }, + new object[] { "'a' >= 'b'", Bool.False }, + new object[] { "'a' >= 'a'", Bool.True }, + new object[] { "'b' >= 'a'", Bool.True }, + new object[] { "'a' > none", Bool.Undefined }, - static readonly IList Data = new List - { - new object[] { "is_defined(3 + none)" }, - new object[] { "is_defined(3 + null_value)" }, - new object[] { "is_defined(true + 3)" }, - new object[] { "is_defined(3 + true)" }, - new object[] { "is_defined(3 + undefined)" }, - new object[] { "is_defined('a' + 'b')" }, - new object[] { "is_defined(undefined + 3)" }, - new object[] { "is_defined(undefined + undefined)" }, - new object[] { "is_defined(3 - none)" }, - new object[] { "is_defined(3 - null_value)" }, - new object[] { "is_defined(true - 3)" }, - new object[] { "is_defined(3 - true)" }, - new object[] { "is_defined(3 - undefined)" }, - new object[] { "is_defined('a' - 'b')" }, - new object[] { "is_defined(undefined - 3)" }, - new object[] { "is_defined(undefined - undefined)" }, - new object[] { "is_defined(3 * none)" }, - new object[] { "is_defined(3 * null_value)" }, - new object[] { "is_defined(true * 3)" }, - new object[] { "is_defined(3 * true)" }, - new object[] { "is_defined(3 * undefined)" }, - new object[] { "is_defined('a' * 'b')" }, - new object[] { "is_defined(undefined * 3)" }, - new object[] { "is_defined(undefined * undefined)" }, - new object[] { "is_defined(3 / none)" }, - new object[] { "is_defined(3 / null_value)" }, - new object[] { "is_defined(true / 3)" }, - new object[] { "is_defined(3 / true)" }, - new object[] { "is_defined(3 / undefined)" }, - new object[] { "is_defined('a' / 'b')" }, - new object[] { "is_defined(undefined / 3)" }, - new object[] { "is_defined(undefined / undefined)" }, - new object[] { "is_defined(3 % none)" }, - new object[] { "is_defined(3 % null_value)" }, - new object[] { "is_defined(true % 3)" }, - new object[] { "is_defined(3 % true)" }, - new object[] { "is_defined(3 % undefined)" }, - new object[] { "is_defined('a' % 'b')" }, - new object[] { "is_defined(undefined % 3)" }, - new object[] { "is_defined(undefined % undefined)" }, - new object[] { "is_defined(3 = none)" }, - new object[] { "is_defined(3 != none)" }, - new object[] { "is_defined(3 <> none)" }, - new object[] { "is_defined(3 > none)" }, - new object[] { "is_defined(3 >= none)" }, - new object[] { "is_defined(3 < none)" }, - new object[] { "is_defined(3 <= none)" }, - new object[] { "is_defined(3 <= none)" }, - new object[] { "is_defined(3 <= none.key1)" }, - new object[] { "is_defined(3 <= none.key1.key2)" }, - new object[] { "is_defined(3 = null_value)" }, - new object[] { "is_defined(3 != null_value)" }, - new object[] { "is_defined(3 <> null_value)" }, - new object[] { "is_defined(3 > null_value)" }, - new object[] { "is_defined(3 >= null_value)" }, - new object[] { "is_defined(3 < null_value)" }, - new object[] { "is_defined(3 <= null_value)" }, - new object[] { "is_defined(3 <= null_value)" }, - // TODO = These tests don't pass at the moment, need to fix them. Might have to look into fixing the grammar. - // Note - looks like Antlr code has changed internally from the version used in IoTHub codebase - // which might affect the behavior. - //new object[] { "3 $ 4" }, - //new object[] { "$ sys1 = 'sysvalue1'" }, + // Access + new object[] { "key1 = \"value1\"", Bool.True }, + new object[] { "KEY1 = \"value1\"", Bool.True }, + new object[] { "key1 = \"value1\" and key1 = \"value1\"", Bool.True }, + new object[] { "key1 = \"value2\" or key1 = \"value1\"", Bool.True }, + new object[] { "key1 = \"value2\"", Bool.False }, + new object[] { "key2 = \"value1\"", Bool.False }, + new object[] { "none = \"value1\"", Bool.Undefined }, + new object[] { "$sys4 = \"value4\"", Bool.True }, + new object[] { "{$sys4} = \"sysvalue4\"", Bool.True }, - // Coalesce - new object[] { "key1 ?? \"hello\" = \"value1\"" }, - new object[] { "(key1 ?? 12.34)" }, - new object[] { "(12.34 ?? key1)" }, - new object[] { "(as_number(key2) ?? key1)" }, - new object[] { "(key1 ?? null)" }, - new object[] { "(null && key1)" }, - new object[] { "(key1 ?? true)" }, - new object[] { "(true && key1)" }, + // system properties + new object[] { "$sys1 = \"sysvalue1\"", Bool.True }, + new object[] { "{$sys1} = \"sysvalue1\"", Bool.True }, + new object[] { "{ $sys1 } = \"sysvalue1\"", Bool.True }, - // Concat - new object[] { "concat() = 'avalue1d'" }, - new object[] { "concat('a') = 'avalue1d'" }, - new object[] { "concat(3) = 'avalue1d'" }, - new object[] { "concat('a', null) = 'avalue1d'" }, - new object[] { "concat('a', undefined) = 'avalue1d'" }, - new object[] { "concat('a', true) = 'avalue1d'" }, - new object[] { "'a' || null = 'avalue1d'" }, - new object[] { "'a' || undefined = 'avalue1d'" }, - new object[] { "'a' || true = 'avalue1d'" }, + // as_number + new object[] { "as_number(\"3\") = 3", Bool.True }, + new object[] { "AS_NUMBER(\"3\") = 3", Bool.True }, + new object[] { "AS_NUMBER('10e10') = 10e10", Bool.True }, + new object[] { "AS_NUMBER('10e1000') = 1/0", Bool.Undefined }, + new object[] { "AS_NUMBER('10000000000000000000000000') = 1e25", Bool.True }, + new object[] { "as_number(key2) = 3", Bool.True }, + new object[] { "as_number(key1) = 3", Bool.Undefined }, + new object[] { "as_number(key3) = 3", Bool.Undefined }, + new object[] { "as_number(key2) * 3 = 9", Bool.True }, + new object[] { "as_number(key3) * 3 = 9", Bool.Undefined }, + new object[] { "as_number(none) * 3 = 9", Bool.Undefined }, + new object[] { "as_number(null_value) * 3 = 9", Bool.Undefined }, + new object[] { "as_number(as_number(4)) * 3 = 12", Bool.True }, - // Length - new object[] { "length(12.34) = 'hello'" }, - new object[] { "length(null) = 'hello'" }, - new object[] { "length(undefined) = 'value1'" }, - new object[] { "length(true) = 'value1'" }, - new object[] { "length(false) = 'value1'" }, + // is_bool + new object[] { "is_bool(12.34)", Bool.False }, + new object[] { "is_bool('not a number')", Bool.False }, + new object[] { "is_bool(\"not a number\")", Bool.False }, + new object[] { "is_bool(true)", Bool.True }, + new object[] { "IS_BOOL(true)", Bool.True }, + new object[] { "is_bool(false)", Bool.True }, + new object[] { "is_bool(none)", Bool.False }, + new object[] { "is_bool(key1)", Bool.False }, - // Lower - new object[] { "lower(12.34) = 'hello'" }, - new object[] { "lower(null) = 'hello'" }, - new object[] { "lower(undefined) = 'value1'" }, - new object[] { "lower(true) = 'value1'" }, - new object[] { "lower(false) = 'value1'" }, + // is_defined + new object[] { "is_defined(undefined)", Bool.False }, + new object[] { "IS_DEFINED(undefined)", Bool.False }, + new object[] { "is_defined(key1)", Bool.True }, + new object[] { "is_defined(key4)", Bool.False }, + new object[] { "is_defined(3 * as_number(key4))", Bool.False }, + new object[] { "is_defined(undefined and true)", Bool.False }, + new object[] { "is_defined(as_number(\"3\"))", Bool.True }, + new object[] { "is_defined(as_number(\"apple\"))", Bool.False }, + new object[] { "is_defined(null_value)", Bool.True }, + new object[] { "is_defined(null)", Bool.True }, + new object[] { "is_defined(10e1000)", Bool.False }, - // Upper - new object[] { "upper(12.34) = 'hello'" }, - new object[] { "upper(null) = 'hello'" }, - new object[] { "upper(undefined) = 'value1'" }, - new object[] { "upper(true) = 'value1'" }, - new object[] { "upper(false) = 'value1'" }, + // is_null + new object[] { "is_null(12.34)", Bool.False }, + new object[] { "is_null('not a number')", Bool.False }, + new object[] { "is_null(\"not a number\")", Bool.False }, + new object[] { "is_null(true)", Bool.False }, + new object[] { "is_null(false)", Bool.False }, + new object[] { "is_null(3 / 0)", Bool.False }, + new object[] { "is_null(as_number(key2))", Bool.False }, + new object[] { "is_null(as_number(key1))", Bool.False }, + new object[] { "is_null(as_number(key1) / as_number(key2))", Bool.False }, + new object[] { "is_null(null)", Bool.True }, + new object[] { "IS_NULL(null)", Bool.True }, + new object[] { "is_null(null_value)", Bool.True }, + new object[] { "is_null(none)", Bool.False }, - // substring - new object[] { "substring() = 'hello'" }, - new object[] { "substring('hello') = 'hello'" }, - new object[] { "substring('hello', 'a') = 'hello'" }, - new object[] { "substring('hello', 'a', 'b') = 'hello'" }, - new object[] { "substring(12.34) = 'hello'" }, - new object[] { "substring(null) = 'hello'" }, - new object[] { "substring(undefined) = 'value1'" }, - new object[] { "substring(true) = 'value1'" }, - new object[] { "substring(false) = 'value1'" }, + // is_number + new object[] { "is_number(12.34)", Bool.True }, + new object[] { "IS_NUMBER(12.34)", Bool.True }, + new object[] { "is_number('not a number')", Bool.False }, + new object[] { "is_number(\"not a number\")", Bool.False }, + new object[] { "is_number(true)", Bool.False }, + new object[] { "is_number(false)", Bool.False }, + new object[] { "is_number(3 / 0)", Bool.False }, + new object[] { "is_number(as_number(key2))", Bool.True }, + new object[] { "is_number(as_number(key1))", Bool.False }, + new object[] { "is_number(as_number(key1) / as_number(key2))", Bool.False }, - // index_of - new object[] { "index_of()" }, - new object[] { "index_of('hello')" }, - new object[] { "index_of('hello', 'a', 'b')" }, - new object[] { "index_of('hello', 12.34)" }, - new object[] { "index_of(12.34, 'hello')" }, - new object[] { "index_of(12.34, 12.34)" }, - new object[] { "index_of(null, 'hello')" }, - new object[] { "index_of('hello', null)" }, - new object[] { "index_of(null, null)" }, - new object[] { "index_of(undefined, 'hello')" }, - new object[] { "index_of('hello', undefined)" }, - new object[] { "index_of(undefined, undefined)" }, - new object[] { "index_of(true, 'hello')" }, - new object[] { "index_of('hello', true)" }, - new object[] { "index_of(true, true)" }, + // is_string + new object[] { "is_string(12.34)", Bool.False }, + new object[] { "is_string('not a number')", Bool.True }, + new object[] { "IS_STRING('not a number')", Bool.True }, + new object[] { "is_string(\"not a number\")", Bool.True }, + new object[] { "is_string(true)", Bool.False }, + new object[] { "is_string(false)", Bool.False }, + new object[] { "is_string(none)", Bool.False }, + new object[] { "is_string(key1)", Bool.True }, + new object[] { "is_string(null_value)", Bool.False }, + new object[] { "is_string('a' || 'b')", Bool.True }, - // starts_with - new object[] { "starts_with()" }, - new object[] { "starts_with('hello')" }, - new object[] { "starts_with('hello', 'a', 'b')" }, - new object[] { "starts_with('hello', 12.34)" }, - new object[] { "starts_with(12.34, 'hello')" }, - new object[] { "starts_with(12.34, 12.34)" }, - new object[] { "starts_with(null, 'hello')" }, - new object[] { "starts_with('hello', null)" }, - new object[] { "starts_with(null, null)" }, - new object[] { "starts_with(undefined, 'hello')" }, - new object[] { "starts_with('hello', undefined)" }, - new object[] { "starts_with(undefined, undefined)" }, - new object[] { "starts_with(true, 'hello')" }, - new object[] { "starts_with('hello', true)" }, - new object[] { "starts_with(true, true)" }, + // concat + new object[] { "concat('a', 'b', 'c', 'd') = 'abcd'", Bool.True }, + new object[] { "CONCAT('a', 'b', 'c', 'd') = 'abcd'", Bool.True }, + new object[] { "concat('a', 'b') = concat('a', 'b')", Bool.True }, + new object[] { "concat('a', key1, 'd') = 'avalue1d'", Bool.True }, + new object[] { "is_defined(concat('a', none, 'd'))", Bool.False }, + new object[] { "'a' || 'b' || 'c' || 'd' = 'abcd'", Bool.True }, + new object[] { "'a' || key1 || 'd' = 'avalue1d'", Bool.True }, + new object[] { "'a' || null_value || 'd' = 'ad'", Bool.True }, + new object[] { "concat('a', null_value, 'd') = 'ad'", Bool.True }, + new object[] { "is_defined('a' || none || 'd')", Bool.False }, + new object[] { "is_defined('a' || null_value || 'd')", Bool.True }, - // ends_with - new object[] { "ends_with()" }, - new object[] { "ends_with('hello')" }, - new object[] { "ends_with('hello', 'a', 'b')" }, - new object[] { "ends_with('hello', 12.34)" }, - new object[] { "ends_with(12.34, 'hello')" }, - new object[] { "ends_with(12.34, 12.34)" }, - new object[] { "ends_with(null, 'hello')" }, - new object[] { "ends_with('hello', null)" }, - new object[] { "ends_with(null, null)" }, - new object[] { "ends_with(undefined, 'hello')" }, - new object[] { "ends_with('hello', undefined)" }, - new object[] { "ends_with(undefined, undefined)" }, - new object[] { "ends_with(true, 'hello')" }, - new object[] { "ends_with('hello', true)" }, - new object[] { "ends_with(true, true)" }, + // length + new object[] { "length('HELLO') = 5", Bool.True }, + new object[] { "LENGTH('') = 0", Bool.True }, + new object[] { "length(key3) = 6", Bool.True }, + new object[] { "is_defined(length(null_value))", Bool.False }, + new object[] { "is_defined(length(none))", Bool.False }, - // contains - new object[] { "contains()" }, - new object[] { "contains('hello')" }, - new object[] { "contains('hello', 'a', 'b')" }, - new object[] { "contains('hello', 12.34)" }, - new object[] { "contains(12.34, 'hello')" }, - new object[] { "contains(12.34, 12.34)" }, - new object[] { "contains(null, 'hello')" }, - new object[] { "contains('hello', null)" }, - new object[] { "contains(null, null)" }, - new object[] { "contains(undefined, 'hello')" }, - new object[] { "contains('hello', undefined)" }, - new object[] { "contains(undefined, undefined)" }, - new object[] { "contains(true, 'hello')" }, - new object[] { "contains('hello', true)" }, - new object[] { "contains(true, true)" }, + // lower + new object[] { "lower('HELLO') = 'hello'", Bool.True }, + new object[] { "LOWER('hello') = 'hello'", Bool.True }, + new object[] { "lower(key3) = 'value3'", Bool.True }, + new object[] { "is_defined(lower(null_value))", Bool.False }, + new object[] { "is_defined(lower(none))", Bool.False }, - // Abs - new object[] { "is_defined(abs(null))" }, - new object[] { "is_defined(abs(undefined))" }, - new object[] { "is_defined(abs(true))" }, - new object[] { "is_defined(abs(false))" }, - new object[] { "is_defined(abs(key1))" }, - new object[] { "is_defined(abs(3, 4))" }, + // upper + new object[] { "upper('HELLO') = 'HELLO'", Bool.True }, + new object[] { "UPPER('hello') = 'HELLO'", Bool.True }, + new object[] { "upper(key1) = 'VALUE1'", Bool.True }, + new object[] { "is_defined(upper(null_value))", Bool.False }, + new object[] { "is_defined(upper(none))", Bool.False }, - // Exp - new object[] { "is_defined(exp(null))" }, - new object[] { "is_defined(exp(undefined))" }, - new object[] { "is_defined(exp(true))" }, - new object[] { "is_defined(exp(false))" }, - new object[] { "is_defined(exp(key1))" }, - new object[] { "is_defined(exp(3, 4))" }, + // substring + new object[] { "substring('abc', 1) = 'bc'", Bool.True }, + new object[] { "substring('abc', 2) = 'c'", Bool.True }, + new object[] { "SUBSTRING('abc', 3) = 'c'", Bool.False }, + new object[] { "SUBSTRING('abc', 4) = 'c'", Bool.Undefined }, + new object[] { "substring(key1, 1) = 'alue1'", Bool.True }, + new object[] { "substring('abc', -1) = ''", Bool.Undefined }, + new object[] { "substring('abc', 1 % 0) = ''", Bool.Undefined }, + new object[] { "substring(null_value, 1) = 'a'", Bool.Undefined }, + new object[] { "substring(none, 1) = 'a'", Bool.Undefined }, - // Power - new object[] { "is_defined(power(null, 2))" }, - new object[] { "is_defined(power(2, null))" }, - new object[] { "is_defined(power(null, null))" }, - new object[] { "is_defined(power(undefined, 2))" }, - new object[] { "is_defined(power(2, undefined))" }, - new object[] { "is_defined(power(undefined, undefined))" }, - new object[] { "is_defined(power(true, 2))" }, - new object[] { "is_defined(power(2, true))" }, - new object[] { "is_defined(power(true, true))" }, - new object[] { "is_defined(power(key1, 2))" }, - new object[] { "is_defined(power(2, key1))" }, - new object[] { "is_defined(power(2, 3, 4))" }, + new object[] { "substring('abc', 1, 1) = 'b'", Bool.True }, + new object[] { "substring('abc', 1, 0) = ''", Bool.True }, + new object[] { "SUBSTRING(key1, 1, 1) = 'a'", Bool.True }, + new object[] { "substring('abc', -1, 0) = ''", Bool.Undefined }, + new object[] { "substring('abc', 1, -2) = ''", Bool.Undefined }, + new object[] { "substring('abc', 1 % 0, 1) = ''", Bool.Undefined }, + new object[] { "substring('abc', 1, 1 % 0) = ''", Bool.Undefined }, + new object[] { "substring(null_value, 1, 1) = 'a'", Bool.Undefined }, + new object[] { "substring(none, 1, 1) = 'a'", Bool.Undefined }, - // Square - new object[] { "is_defined(square(null))" }, - new object[] { "is_defined(square(undefined))" }, - new object[] { "is_defined(square(true))" }, - new object[] { "is_defined(square(false))" }, - new object[] { "is_defined(square(key1))" }, - new object[] { "is_defined(square(3, 3))" }, + new object[] { "substring('hello', 0, 5) = 'hello'", Bool.True }, + new object[] { "substring('start', 0, 4) = 'star'", Bool.True }, + new object[] { "substring('hello', 1, 4) = 'ello'", Bool.True }, + new object[] { "substring('hello', 4, 1) = 'o'", Bool.True }, + new object[] { "substring('hello', 4, 0) = ''", Bool.True }, - // Ceiling - new object[] { "is_defined(ceiling(null))" }, - new object[] { "is_defined(ceiling(undefined))" }, - new object[] { "is_defined(ceiling(true))" }, - new object[] { "is_defined(ceiling(false))" }, - new object[] { "is_defined(ceiling(key1))" }, - new object[] { "is_defined(ceiling(3, 3))" }, + // index_of + new object[] { "index_of('abcdef', 'cd') = 2", Bool.True }, + new object[] { "index_of('abc', 'xz') = -1", Bool.True }, + new object[] { "INDEX_OF(key1, 'val') = 0", Bool.True }, + new object[] { "index_of('lue1value1', key1) = 4", Bool.True }, + new object[] { "index_of('abc', key1) = -1", Bool.True }, + new object[] { "index_of(null_value, '1') = 0", Bool.Undefined }, + new object[] { "index_of(none, '1') = 0", Bool.Undefined }, - // Floor - new object[] { "is_defined(floor(null))" }, - new object[] { "is_defined(floor(undefined))" }, - new object[] { "is_defined(floor(true))" }, - new object[] { "is_defined(floor(false))" }, - new object[] { "is_defined(floor(key1))" }, - new object[] { "is_defined(floor(3, 3))" }, + // starts_with + new object[] { "starts_with('abc', 'ab')", Bool.True }, + new object[] { "starts_with('abc', 'xz')", Bool.False }, + new object[] { "STARTS_WITH(key1, 'val')", Bool.True }, + new object[] { "starts_with('value1value1', key1)", Bool.True }, + new object[] { "starts_with('abc', key1)", Bool.False }, + new object[] { "starts_with(null_value, '1')", Bool.Undefined }, + new object[] { "starts_with(none, '1')", Bool.Undefined }, - // Sign - new object[] { "is_defined(sign(null))" }, - new object[] { "is_defined(sign(undefined))" }, - new object[] { "is_defined(sign(true))" }, - new object[] { "is_defined(sign(false))" }, - new object[] { "is_defined(sign(key1))" }, - new object[] { "is_defined(sign(3, 3))" }, + // ends_with + new object[] { "ends_with('abc', 'bc')", Bool.True }, + new object[] { "ends_with('abc', 'xz')", Bool.False }, + new object[] { "ENDS_WITH(key1, 'e1')", Bool.True }, + new object[] { "ends_with('value1value1', key1)", Bool.True }, + new object[] { "ends_with('abc', key1)", Bool.False }, + new object[] { "ends_with(null_value, '1')", Bool.Undefined }, + new object[] { "ends_with(none, '1')", Bool.Undefined }, - // Sqrt - new object[] { "is_defined(sqrt(null))" }, - new object[] { "is_defined(sqrt(undefined))" }, - new object[] { "is_defined(sqrt(true))" }, - new object[] { "is_defined(sqrt(false))" }, - new object[] { "is_defined(sqrt(key1))" }, - new object[] { "is_defined(sqrt(3, 3))" }, + // contains + new object[] { "contains('abc', 'ab')", Bool.True }, + new object[] { "contains('abc', 'xz')", Bool.False }, + new object[] { "CONTAINS(key1, 'val')", Bool.True }, + new object[] { "contains('value1value1', key1)", Bool.True }, + new object[] { "contains('abc', key1)", Bool.False }, + new object[] { "contains(null_value, '1')", Bool.Undefined }, + new object[] { "contains(none, '1')", Bool.Undefined }, - new object[] { "test == true" }, - new object[] { "true == test" }, + // abs + new object[] { "abs(-12) = 12", Bool.True }, + new object[] { "abs(12) = 12", Bool.True }, + new object[] { "ABS(as_number(key2)) = 3", Bool.True }, + new object[] { "is_defined(abs(as_number(key1)))", Bool.False }, + new object[] { "is_defined(abs(3 / 0))", Bool.True }, + new object[] { "is_defined(abs(3 % 0))", Bool.False }, - // empty unterminated string - new object[] { "none = \""}, + // exp + new object[] { "exp(0) = 1", Bool.True }, + new object[] { "abs(exp(1) - 2.718281) < 1e-6", Bool.True }, + new object[] { "abs(EXP(as_number(key2)) - 20.085536) < 1e-6", Bool.True }, + new object[] { "exp(4000) = 3/0", Bool.True }, + new object[] { "is_defined(exp(as_number(key1)))", Bool.False }, + new object[] { "is_defined(exp(3 / 0))", Bool.True }, + new object[] { "is_defined(exp(3 % 0))", Bool.False }, - // and - new object[] { "none and true" }, - new object[] { "undefined and \"hello\"" }, + // power + new object[] { "power(2, 3) = 8", Bool.True }, + new object[] { "POWER(3, 2) = 9", Bool.True }, + new object[] { "POWER(10, 4000) = 3/0", Bool.True }, + new object[] { "power(as_number(key2), as_number(key2)) = 27", Bool.True }, + new object[] { "is_defined(power(as_number(key1), as_number(key1)))", Bool.False }, + new object[] { "is_defined(power(3 / 0, 2))", Bool.True }, + new object[] { "is_defined(power(2, 3 / 0))", Bool.True }, + new object[] { "is_defined(power(3 % 0, 2))", Bool.False }, + new object[] { "is_defined(power(2, 3 % 0))", Bool.False }, - // or - new object[] { "none or true" }, + // square + new object[] { "square(2) = 4", Bool.True }, + new object[] { "SQUARE(3) = 9", Bool.True }, + new object[] { "square(as_number(key2)) = 9", Bool.True }, + new object[] { "is_defined(square(as_number(key1)))", Bool.False }, + new object[] { "is_defined(square(3 / 0))", Bool.True }, + new object[] { "is_defined(square(3 % 0))", Bool.False }, - // negate - new object[] { "- (key1)" }, + // ceiling + new object[] { "ceiling(2) = 2", Bool.True }, + new object[] { "ceiling(2.1) = 3", Bool.True }, + new object[] { "ceiling(-2.1) = -2", Bool.True }, + new object[] { "CEILING(-2) = -2", Bool.True }, + new object[] { "ceiling(as_number(key2)) = 3", Bool.True }, + new object[] { "is_defined(ceiling(as_number(key1)))", Bool.False }, + new object[] { "is_defined(ceiling(3 / 0))", Bool.True }, + new object[] { "is_defined(ceiling(3 % 0))", Bool.False }, - // not - new object[] { "is_defined(not key1)"}, + // floor + new object[] { "floor(2) = 2", Bool.True }, + new object[] { "floor(2.1) = 2", Bool.True }, + new object[] { "floor(-2.1) = -3", Bool.True }, + new object[] { "FLOOR(-2) = -2", Bool.True }, + new object[] { "floor(as_number(key2)) = 3", Bool.True }, + new object[] { "is_defined(floor(as_number(key1)))", Bool.False }, + new object[] { "is_defined(floor(3 / 0))", Bool.True }, + new object[] { "is_defined(floor(3 % 0))", Bool.False }, + + // sign + new object[] { "sign(0) = 0", Bool.True }, + new object[] { "sign(2) = 1", Bool.True }, + new object[] { "sign(2.1) = 1", Bool.True }, + new object[] { "sign(-2.1) = -1", Bool.True }, + new object[] { "SIGN(-2) = -1", Bool.True }, + new object[] { "sign(as_number(key2)) = 1", Bool.True }, + new object[] { "is_defined(sign(as_number(key1)))", Bool.False }, + new object[] { "sign(3 / 0) = 1", Bool.True }, + new object[] { "sign(-3 / 0) = -1", Bool.True }, + new object[] { "is_defined(sign(3 % 0))", Bool.False }, + + // sqrt + new object[] { "sqrt(4) = 2", Bool.True }, + new object[] { "SQRT(-4) = -2", Bool.Undefined }, + new object[] { "abs(sqrt(as_number(key2)) - 1.732050) < 1e-6", Bool.True }, + new object[] { "is_defined(sqrt(as_number(key1)))", Bool.False }, + new object[] { "is_defined(sqrt(3 / 0))", Bool.True }, + new object[] { "is_defined(sqrt(3 % 0))", Bool.False }, }; - } - [Theory, Unit] - [MemberData(nameof(TestCompilationFailureDataSource.TestData), MemberType = typeof(TestCompilationFailureDataSource))] - public void TestCompilationFailure(string condition) - { - var route = new Route("id", condition, "hub", TelemetryMessageSource.Instance, new HashSet()); - Assert.Throws(() => RouteCompiler.Instance.Compile(route)); + public static IEnumerable TestData => Data; } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionVisitorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionVisitorTest.cs index e9e834ccba3..7407ce8c926 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionVisitorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ConditionVisitorTest.cs @@ -8,16 +8,16 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System.Linq.Expressions; using Antlr4.Runtime; using Antlr4.Runtime.Tree; - using Microsoft.Azure.Devices.Routing.Core; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; [ExcludeFromCodeCoverage] public class ConditionVisitorTest : RoutingUnitTestBase { - [Theory, Unit] + [Theory] + [Unit] [InlineData("100", 100D)] [InlineData("0100", 100D)] [InlineData("(((3)))", 3D)] @@ -40,7 +40,8 @@ public void TestLiteral(string condition, object value) Assert.Equal(value, ((ConstantExpression)expression).Value); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("3 + 4", 7)] [InlineData("3 + 4 * 5", 23)] [InlineData("3 + 4 / 4", 4)] @@ -64,7 +65,8 @@ public void TestArithmetic(string condition, double expected) Assert.Equal(expected, rule()); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("\"unterminated = 'unterminated'", "Syntax error: unterminated string")] [InlineData("@32 = 32", "Syntax error: invalid symbol '@'")] [InlineData("32 = [32]", "Syntax error.")] @@ -90,7 +92,8 @@ static Expression ToExpression(string condition) ParameterExpression parameter = Expression.Parameter(typeof(IMessage), "message"); - var testRoute = new Route(Guid.NewGuid().ToString(), + var testRoute = new Route( + Guid.NewGuid().ToString(), "true", nameof(ConditionVisitorTest), TelemetryMessageSource.Instance, diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ErrorListenerTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ErrorListenerTest.cs index 8ea69dee8dc..d199a21cf9d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ErrorListenerTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/ErrorListenerTest.cs @@ -4,16 +4,17 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; - using Microsoft.Azure.Devices.Routing.Core.Query; - using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; + using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Xunit; [ExcludeFromCodeCoverage] public class ErrorListenerTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void TestOperandError() { string condition = "3 + '4' = 7"; @@ -28,7 +29,8 @@ public void TestOperandError() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestArgumentError() { string condition = "as_number(true) = true"; @@ -43,7 +45,8 @@ public void TestArgumentError() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestInvalidBuiltinError() { string condition = "nope(true) = true"; @@ -58,7 +61,8 @@ public void TestInvalidBuiltinError() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestSyntaxErrorMissingParens() { string condition = "(2 + 22 = 24"; @@ -73,7 +77,8 @@ public void TestSyntaxErrorMissingParens() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestSyntaxErrorMissingParensFunc() { string condition = "as_number(\"2\" = 24"; @@ -88,7 +93,8 @@ public void TestSyntaxErrorMissingParensFunc() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestSyntaxErrorExtraParens() { string condition = "(2 + 22 )) = 24"; @@ -103,7 +109,8 @@ public void TestSyntaxErrorExtraParens() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestSyntaxErrorExtraParensFunc() { string condition = "as_number(\"2\")) = 24"; @@ -118,7 +125,8 @@ public void TestSyntaxErrorExtraParensFunc() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestSyntaxErrorUnterminatedString() { string condition = "\"2 = 24"; @@ -133,7 +141,8 @@ public void TestSyntaxErrorUnterminatedString() Assert.Equal(ErrorSeverity.Error, error1.Severity); } - [Fact, Unit] + [Fact] + [Unit] public void TestSyntaxErrorUnrecognizedSymbol() { string condition = "3 = @ 3"; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/NullTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/NullTest.cs index 6216c863cec..eef648cb8ac 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/NullTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/NullTest.cs @@ -4,14 +4,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; [ExcludeFromCodeCoverage] public class NullTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void SmokeTest() { BinaryExpression expression = Expression.LessThan(Expression.Add(Expression.Constant(3.0, typeof(double)), Expression.Constant(Null.Instance)), Expression.Constant(4.0)); @@ -19,7 +20,8 @@ public void SmokeTest() Assert.True(rule()); } - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { var d1 = new Null(); @@ -33,7 +35,8 @@ public void TestEquals() Assert.Equal(Bool.Undefined, d1 == Bool.False); } - [Fact, Unit] + [Fact] + [Unit] public void TestArthimetic() { var d1 = new Null(); @@ -57,7 +60,8 @@ public void TestArthimetic() Assert.Equal(double.NaN, d1 / d2); } - [Fact, Unit] + [Fact] + [Unit] public void TestComparison() { var d1 = new Null(); @@ -96,7 +100,8 @@ public void TestComparison() Assert.Equal(Bool.True, d1 >= d2); } - [Fact, Unit] + [Fact] + [Unit] public void TestExpression() { // Add diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/QueryValueTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/QueryValueTest.cs index 186d46a98dd..b299c201d94 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/QueryValueTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/QueryValueTest.cs @@ -2,9 +2,9 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query { using System; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Routing.Core.Query.Types; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; public class QueryValueTest @@ -19,7 +19,8 @@ public class QueryValueTest static readonly QueryValue StringQueryValue = new QueryValue(DefaultRandomString, QueryValueType.String); static readonly QueryValue ObjectQueryValue = new QueryValue(DefaultEmptyObject, QueryValueType.Object); - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_Undefined() { Assert.True(UndefinedQueryValue.CompareTo(QueryValue.Undefined) != 0); @@ -36,7 +37,8 @@ public void QueryValue_Undefined() Assert.True(UndefinedQueryValue.CompareTo(new InvalidOperationException()) != 0); } - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_Null() { Assert.Equal(NullQueryValue, QueryValue.Null); @@ -54,7 +56,8 @@ public void QueryValue_Null() Assert.True(NullQueryValue.CompareTo(new InvalidOperationException()) != 0); } - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_Bool() { Assert.True(BoolQueryValue.CompareTo(Bool.True) == 0); @@ -73,7 +76,8 @@ public void QueryValue_Bool() Assert.True(BoolQueryValue.CompareTo(new InvalidOperationException()) != 0); } - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_Double() { Assert.True(DoubleQueryValue.CompareTo(15.0) < 0); @@ -94,13 +98,15 @@ public void QueryValue_Double() Assert.True(longDoubleQueryValue.CompareTo(123) != 0); } - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_String() { string stringToCompare = Guid.NewGuid().ToString(); Assert.True(StringQueryValue.CompareTo(DefaultRandomString) == 0); - Assert.Equal(StringQueryValue.CompareTo(stringToCompare), + Assert.Equal( + StringQueryValue.CompareTo(stringToCompare), string.Compare(DefaultRandomString, stringToCompare, StringComparison.Ordinal)); Assert.True(StringQueryValue.CompareTo(BoolQueryValue) != 0); @@ -115,14 +121,15 @@ public void QueryValue_String() Assert.True(StringQueryValue.CompareTo(new InvalidOperationException()) != 0); } - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_Object() { var objectToCompare = new object(); Assert.True(ObjectQueryValue.CompareTo(ObjectQueryValue) == 0); Assert.True(ObjectQueryValue.CompareTo(objectToCompare) != 0); - Assert.True(ObjectQueryValue.CompareTo(DefaultEmptyObject) != 0); //Fail because comparison is not on a QueryValue object. + Assert.True(ObjectQueryValue.CompareTo(DefaultEmptyObject) != 0); // Fail because comparison is not on a QueryValue object. Assert.True(ObjectQueryValue.CompareTo(BoolQueryValue) != 0); Assert.True(ObjectQueryValue.CompareTo(UndefinedQueryValue) != 0); @@ -140,7 +147,8 @@ public void QueryValue_Object() Assert.True(ObjectQueryValue.CompareTo(new InvalidOperationException()) != 0); } - [Fact, Unit] + [Fact] + [Unit] public void QueryValue_None() { var noneQueryValue = new QueryValue(null, QueryValueType.None); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryI18NTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryI18NTest.cs index 73d09e2f918..7c96a8e0aef 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryI18NTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryI18NTest.cs @@ -1,76 +1,78 @@ // Copyright (c) Microsoft. All rights reserved. -//namespace Microsoft.Azure.Devices.Routing.Core.Test.Query -//{ -// using System; -// using System.Collections.Generic; -// using System.Text; -// using Microsoft.Azure.Devices.Common.Api; -// using Microsoft.Azure.Devices.DeviceManagement.Model; -// using Microsoft.Azure.Devices.Routing.Core.Query; -// using Microsoft.Azure.Devices.Edge.Util.Test.Common; -// using Xunit; +/* +namespace Microsoft.Azure.Devices.Routing.Core.Test.Query +{ + using System; + using System.Collections.Generic; + using System.Text; + using Microsoft.Azure.Devices.Common.Api; + using Microsoft.Azure.Devices.DeviceManagement.Model; + using Microsoft.Azure.Devices.Routing.Core.Query; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Xunit; -// public class TwinBodyQueryI18NTest : RoutingUnitTestBase -// { -// public TwinBodyQueryI18NTest() -// { -// Message1 = GenerateDefaultTwinMessage(); -// } + public class TwinBodyQueryI18NTest : RoutingUnitTestBase + { + public TwinBodyQueryI18NTest() + { + Message1 = GenerateDefaultTwinMessage(); + } -// IMessage Message1; -// IMessage GenerateDefaultTwinMessage() -// { -// var twinInfo = new DeviceTwinInfo(); -// twinInfo.Tags = new DataObject(); -// twinInfo.DesiredProperties = new DeviceTwinProperties(); -// twinInfo.ReportedProperties = new DeviceTwinProperties(); -// var a1 = new DataObject(); -// a1["he"] = "ברוכים הבאים!"; -// a1["es"] = "Bienvenido!"; + IMessage Message1; + IMessage GenerateDefaultTwinMessage() + { + var twinInfo = new DeviceTwinInfo(); + twinInfo.Tags = new DataObject(); + twinInfo.DesiredProperties = new DeviceTwinProperties(); + twinInfo.ReportedProperties = new DeviceTwinProperties(); + var a1 = new DataObject(); + a1["he"] = "ברוכים הבאים!"; + a1["es"] = "Bienvenido!"; -// var a2 = new DataObject(); -// a2["he"] = "אני משתמש ב%1"; -// a2["es"] = "Estoy usando %1"; + var a2 = new DataObject(); + a2["he"] = "אני משתמש ב%1"; + a2["es"] = "Estoy usando %1"; -// var a3 = new DataObject(); -// a3["he"] = "אני משתמש ב%1"; -// a3["es"] = "Estoy usando %1"; + var a3 = new DataObject(); + a3["he"] = "אני משתמש ב%1"; + a3["es"] = "Estoy usando %1"; -// twinInfo.Tags["Welcome!"] = a1; -// twinInfo.Tags["I'm using spaces %1"] = a2; -// twinInfo.Tags["I'mnotusingspaces%1"] = a3; + twinInfo.Tags["Welcome!"] = a1; + twinInfo.Tags["I'm using spaces %1"] = a2; + twinInfo.Tags["I'mnotusingspaces%1"] = a3; -// IMessage twinMessage = new TwinMessage(TwinChangeEventMessageSource.Instance, twinInfo); + IMessage twinMessage = new TwinMessage(TwinChangeEventMessageSource.Instance, twinInfo); -// return twinMessage; -// } + return twinMessage; + } -// [Theory, Unit] -// [InlineData("$body.tags.Welcome!.he = 'ברוכים הבאים!'")] -// [InlineData("$body.tags.I'mnotusingspaces%1.es = 'Estoy usando %1'")] -// public void BodyQueryI18N_Success(string condition) -// { -// var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.True); -// } + [Theory, Unit] + [InlineData("$body.tags.Welcome!.he = 'ברוכים הבאים!'")] + [InlineData("$body.tags.I'mnotusingspaces%1.es = 'Estoy usando %1'")] + public void BodyQueryI18N_Success(string condition) + { + var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.True); + } -// [Theory, Unit] -// [InlineData("$body.tags.Welcome!.I'mnotusingspaces%1.es = 'Estoy usando %1'")] -// public void BodyQueryI18N_Failure(string condition) -// { -// var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.False); -// } + [Theory, Unit] + [InlineData("$body.tags.Welcome!.I'mnotusingspaces%1.es = 'Estoy usando %1'")] + public void BodyQueryI18N_Failure(string condition) + { + var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.False); + } -// [Theory, Unit] -// [InlineData("$body.tags.Welcome!['I'm using spaces %1'] = 'ברוכים הבאים!'")] -// public void BodyQueryI18N_RouteCompilation(string condition) -// { -// var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); -// Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery)); -// } -// } -//} + [Theory, Unit] + [InlineData("$body.tags.Welcome!['I'm using spaces %1'] = 'ברוכים הבאים!'")] + public void BodyQueryI18N_RouteCompilation(string condition) + { + var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); + Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery)); + } + } +} +*/ diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryTest.cs index 769bf9d9c67..6fff3bb9990 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinBodyQueryTest.cs @@ -1,289 +1,289 @@ // Copyright (c) Microsoft. All rights reserved. -//namespace Microsoft.Azure.Devices.Routing.Core.Test.Query -//{ -// using System; -// using System.CodeDom.Compiler; -// using System.Collections.Generic; -// using System.Text; -// using Microsoft.Azure.Devices.Common.Api; -// using Microsoft.Azure.Devices.DeviceManagement.Model; -// using Microsoft.Azure.Devices.Routing.Core.Query; -// using Microsoft.Azure.Devices.Edge.Util.Test.Common; -// using Xunit; +/* +namespace Microsoft.Azure.Devices.Routing.Core.Test.Query +{ + using System; + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Text; + using Microsoft.Azure.Devices.Common.Api; + using Microsoft.Azure.Devices.DeviceManagement.Model; + using Microsoft.Azure.Devices.Routing.Core.Query; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Xunit; -// public class TwinBodyQueryTest: RoutingUnitTestBase -// { -// public TwinBodyQueryTest() -// { -// Message1 = GenerateDefaultTwinMessage(); -// } + public class TwinBodyQueryTest : RoutingUnitTestBase + { + public TwinBodyQueryTest() + { + Message1 = GenerateDefaultTwinMessage(); + } -// IMessage Message1; -// IMessage GenerateDefaultTwinMessage() -// { -// var twinInfo = new DeviceTwinInfo(); -// twinInfo.Tags = new DataObject(); -// twinInfo.DesiredProperties = new DeviceTwinProperties(); -// twinInfo.ReportedProperties = new DeviceTwinProperties(); -// var weather = new DataObject(); - -// weather["Temperature"] = 50; -// weather["FreezingTemperature"] = -50.4; -// weather["PreciseTemperature"] = 50.4; -// weather["Temperature_PropertyConflict"] = 50; -// weather["NullValue"] = null; -// weather["Time"] = DateTime.Parse("2017-03-09T00:00:00.000Z"); -// weather["IsEnabled"] = true; -// weather["IsDisabled"] = false; -// weather["PrevTemperatures_0"] = 20; -// weather["PrevTemperatures_1"] = 30; -// weather["PrevTemperatures_2"] = 40; -// var location = new DataObject(); -// location["Street"] = "One Microsoft Way"; -// location["City"] = "Redmond"; -// location["State"] = "WA"; + IMessage Message1; + IMessage GenerateDefaultTwinMessage() + { + var twinInfo = new DeviceTwinInfo(); + twinInfo.Tags = new DataObject(); + twinInfo.DesiredProperties = new DeviceTwinProperties(); + twinInfo.ReportedProperties = new DeviceTwinProperties(); + var weather = new DataObject(); -// var histori0 = new DataObject(); -// histori0["Month"] = "Feb"; -// histori0["Temperature"] = 40; + weather["Temperature"] = 50; + weather["FreezingTemperature"] = -50.4; + weather["PreciseTemperature"] = 50.4; + weather["Temperature_PropertyConflict"] = 50; + weather["NullValue"] = null; + weather["Time"] = DateTime.Parse("2017-03-09T00:00:00.000Z"); + weather["IsEnabled"] = true; + weather["IsDisabled"] = false; + weather["PrevTemperatures_0"] = 20; + weather["PrevTemperatures_1"] = 30; + weather["PrevTemperatures_2"] = 40; + var location = new DataObject(); + location["Street"] = "One Microsoft Way"; + location["City"] = "Redmond"; + location["State"] = "WA"; -// var histori1 = new DataObject(); -// histori1["Month"] = "Jan"; -// histori1["Temperature"] = 30; + var histori0 = new DataObject(); + histori0["Month"] = "Feb"; + histori0["Temperature"] = 40; -// weather["HistoricalData_0"] = histori0; -// weather["HistoricalData_1"] = histori1; - -// weather["Location"] = location; -// twinInfo.Tags["Weather"] = weather; + var histori1 = new DataObject(); + histori1["Month"] = "Jan"; + histori1["Temperature"] = 30; -// IMessage twinMessage = new TwinMessage(TwinChangeEventMessageSource.Instance, twinInfo, -// new Dictionary -// { -// { "City", "Redmond" }, -// { "$State", "WA" }, -// { "$body.tags.Weather.Temperature_PropertyConflict", "100" }, -// { "IntegerProperty", "110" }, -// }, -// new Dictionary -// { -// { "State", "CA" }, -// { "$body.tags.Weather.Location.State", "CA" }, -// { SystemProperties.ContentEncoding, "UTF-8" }, -// { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, -// }); + weather["HistoricalData_0"] = histori0; + weather["HistoricalData_1"] = histori1; + weather["Location"] = location; + twinInfo.Tags["Weather"] = weather; -// return twinMessage; -// } + IMessage twinMessage = new TwinMessage(TwinChangeEventMessageSource.Instance, twinInfo, + new Dictionary + { + { "City", "Redmond" }, + { "$State", "WA" }, + { "$body.tags.Weather.Temperature_PropertyConflict", "100" }, + { "IntegerProperty", "110" }, + }, + new Dictionary + { + { "State", "CA" }, + { "$body.tags.Weather.Location.State", "CA" }, + { SystemProperties.ContentEncoding, "UTF-8" }, + { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, + }); - -// [Theory, Unit] -// [InlineData("50 >= $body.tags.Weather.Temperature")] -// [InlineData("$body.tags.Weather.Temperature >= 50")] -// [InlineData("$body.tags.Weather.Temperature <= 50")] -// [InlineData("$body.tags.Weather.Temperature = 50")] -// [InlineData("$body.tags.Weather.Temperature != 60")] -// [InlineData("$body.tags.Weather.Temperature <> 500")] -// [InlineData("$body.tags.Weather.Temperature = 40 + 10")] -// [InlineData("$body.tags.Weather.Temperature >= $body.tags.Weather.PrevTemperatures_0")] -// [InlineData("$body.tags.Weather.Temperature >= $body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1 ")] -// [InlineData("$body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1= 50 ")] -// [InlineData("$body.tags.Weather.PrevTemperatures_1 * $body.tags.Weather.PrevTemperatures_2 = 1200")] -// [InlineData("$body.tags.Weather.PrevTemperatures_1 / $body.tags.Weather.PrevTemperatures_0 = 1.5")] -// [InlineData("$body.tags.Weather.HistoricalData_0.Temperature - 10 = $body.tags.Weather.HistoricalData_1.Temperature")] -// [InlineData("$body.tags.Weather.HistoricalData_0.Month = 'Feb'")] -// public void BodyQuery_Double_Success(string condition) -// { -// var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.True); -// } + return twinMessage; + } -// [Theory, Unit] -// [InlineData("10 >= $body.tags.Weather.Temperature")] -// [InlineData("$body.tags.Weather.Temperature < 50")] -// [InlineData("$body.tags.Weather.Temperature = '50'")] // no implicit cross type conversion -// [InlineData("$body.tags.Weather.Temperature != 50")] -// [InlineData("$body.tags.Weather.Temperature = 60")] -// [InlineData("$body.tags.Weather.Temperature <> 40 + 10")] -// [InlineData("$body.tags.Weather.Temperature <= $body.tags.Weather.PrevTemperatures_0")] -// [InlineData("$body.tags.Weather.Temperature < $body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1")] -// [InlineData("$body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1 <> 50 ")] -// [InlineData("$body.tags.Weather.PrevTemperatures_0 * $body.tags.Weather.PrevTemperatures_1 = 610")] -// [InlineData("$body.tags.Weather.PrevTemperatures_1 / $body.tags.Weather.PrevTemperatures_0 = 3/3")] -// [InlineData("$body.tags.Weather.HistoricalData_0.Temperature + 10 = $body.tags.Weather.HistoricalData_1.Temperature")] -// public void BodyQuery_Double_Failure(string condition) -// { -// var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.False); -// } + [Theory, Unit] + [InlineData("50 >= $body.tags.Weather.Temperature")] + [InlineData("$body.tags.Weather.Temperature >= 50")] + [InlineData("$body.tags.Weather.Temperature <= 50")] + [InlineData("$body.tags.Weather.Temperature = 50")] + [InlineData("$body.tags.Weather.Temperature != 60")] + [InlineData("$body.tags.Weather.Temperature <> 500")] + [InlineData("$body.tags.Weather.Temperature = 40 + 10")] + [InlineData("$body.tags.Weather.Temperature >= $body.tags.Weather.PrevTemperatures_0")] + [InlineData("$body.tags.Weather.Temperature >= $body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1 ")] + [InlineData("$body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1= 50 ")] + [InlineData("$body.tags.Weather.PrevTemperatures_1 * $body.tags.Weather.PrevTemperatures_2 = 1200")] + [InlineData("$body.tags.Weather.PrevTemperatures_1 / $body.tags.Weather.PrevTemperatures_0 = 1.5")] + [InlineData("$body.tags.Weather.HistoricalData_0.Temperature - 10 = $body.tags.Weather.HistoricalData_1.Temperature")] + [InlineData("$body.tags.Weather.HistoricalData_0.Month = 'Feb'")] + public void BodyQuery_Double_Success(string condition) + { + var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.True); + } -// [Theory, Unit] -// [InlineData("$body.tags.Weather.Temperature <> '100'")] -// [InlineData("$body.tags.Weather.Location.City = City")] -// [InlineData("$body.tags.Weather.Location.State = $State")] -// [InlineData("$body.tags.Weather.Location.State <> $body.tags.Weather.Location.City")] -// public void BodyQuery_String_Success(string condition) -// { -// var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.True); -// } + [Theory, Unit] + [InlineData("10 >= $body.tags.Weather.Temperature")] + [InlineData("$body.tags.Weather.Temperature < 50")] + [InlineData("$body.tags.Weather.Temperature = '50'")] // no implicit cross type conversion + [InlineData("$body.tags.Weather.Temperature != 50")] + [InlineData("$body.tags.Weather.Temperature = 60")] + [InlineData("$body.tags.Weather.Temperature <> 40 + 10")] + [InlineData("$body.tags.Weather.Temperature <= $body.tags.Weather.PrevTemperatures_0")] + [InlineData("$body.tags.Weather.Temperature < $body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1")] + [InlineData("$body.tags.Weather.PrevTemperatures_0 + $body.tags.Weather.PrevTemperatures_1 <> 50 ")] + [InlineData("$body.tags.Weather.PrevTemperatures_0 * $body.tags.Weather.PrevTemperatures_1 = 610")] + [InlineData("$body.tags.Weather.PrevTemperatures_1 / $body.tags.Weather.PrevTemperatures_0 = 3/3")] + [InlineData("$body.tags.Weather.HistoricalData_0.Temperature + 10 = $body.tags.Weather.HistoricalData_1.Temperature")] + public void BodyQuery_Double_Failure(string condition) + { + var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.False); + } -// [Theory, Unit] -// [InlineData("$body.tags.Weather.Temperature ='100'")] -// [InlineData("$body.tags.Weather.Temperature ='150'")] -// [InlineData("$body.tags.Weather.Location.City != City")] -// [InlineData("$body.tags.Weather.Location.State != $State")] -// [InlineData("$body.tags.Weather.Location.State = City")] -// [InlineData("$body.tags.Weather.Location.State = $body.tags.Weather.Location.City")] -// public void BodyQuery_String_Failure(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.False); -// } + [Theory, Unit] + [InlineData("$body.tags.Weather.Temperature <> '100'")] + [InlineData("$body.tags.Weather.Location.City = City")] + [InlineData("$body.tags.Weather.Location.State = $State")] + [InlineData("$body.tags.Weather.Location.State <> $body.tags.Weather.Location.City")] + public void BodyQuery_String_Success(string condition) + { + var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.True); + } -// [Theory, Unit] -// [InlineData("$body.tags.Weather.IsEnabled")] -// [InlineData("$body.tags.Weather.IsEnabled = true")] -// [InlineData("$body.tags.Weather.IsDisabled = false")] -// [InlineData("$body.tags.Weather.IsDisabled <> true")] -// [InlineData("NOT $body.tags.Weather.IsDisabled")] -// [InlineData("$body.tags.Weather.InvalidKey <> '100'")] -// public void BodyQuery_Bool(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.True); -// } + [Theory, Unit] + [InlineData("$body.tags.Weather.Temperature ='100'")] + [InlineData("$body.tags.Weather.Temperature ='150'")] + [InlineData("$body.tags.Weather.Location.City != City")] + [InlineData("$body.tags.Weather.Location.State != $State")] + [InlineData("$body.tags.Weather.Location.State = City")] + [InlineData("$body.tags.Weather.Location.State = $body.tags.Weather.Location.City")] + public void BodyQuery_String_Failure(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.False); + } -// [Theory, Unit] -// [InlineData("$body.tags.Weather.IsEnabled AND $body.tags.Weather.IsEnabled")] -// [InlineData("$body.tags.Weather.IsEnabled OR $body.tags.Weather.IsDisabled")] -// [InlineData("$body.tags.Weather.IsDisabled OR NOT($body.tags.Weather.IsDisabled)")] -// public void BodyQuery_Logical(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.True); -// } + [Theory, Unit] + [InlineData("$body.tags.Weather.IsEnabled")] + [InlineData("$body.tags.Weather.IsEnabled = true")] + [InlineData("$body.tags.Weather.IsDisabled = false")] + [InlineData("$body.tags.Weather.IsDisabled <> true")] + [InlineData("NOT $body.tags.Weather.IsDisabled")] + [InlineData("$body.tags.Weather.InvalidKey <> '100'")] + public void BodyQuery_Bool(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.True); + } -// [Theory, Unit] -// [InlineData("$body")] -// [InlineData("$body.tags.Weather.HistoricalData_0.Temperature.InvalidKey")] -// [InlineData("$City <> $body.tags.Weather.InvalidKey")] -// public void BodyQuery_Undefined(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.Equal(rule(Message1), Bool.Undefined); -// } + [Theory, Unit] + [InlineData("$body.tags.Weather.IsEnabled AND $body.tags.Weather.IsEnabled")] + [InlineData("$body.tags.Weather.IsEnabled OR $body.tags.Weather.IsDisabled")] + [InlineData("$body.tags.Weather.IsDisabled OR NOT($body.tags.Weather.IsDisabled)")] + public void BodyQuery_Logical(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.True); + } -// [Theory, Unit] -// [InlineData("$body.tags.Weather.Temperature_PropertyConflict = '100'")] -// [InlineData("as_number($body.tags.Weather.Temperature_PropertyConflict) = 100")] -// [InlineData("{$body.tags.Weather.Temperature_PropertyConflict} <> 100")] -// [InlineData("$body.tags.Weather.Location.State = 'WA'")] -// [InlineData("{$body.tags.Weather.Location.State} <> 'CA'")] -// public void BodyQuery_SysPropertyConflict(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + [Theory, Unit] + [InlineData("$body")] + [InlineData("$body.tags.Weather.HistoricalData_0.Temperature.InvalidKey")] + [InlineData("$City <> $body.tags.Weather.InvalidKey")] + public void BodyQuery_Undefined(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Assert.Equal(rule(Message1), Bool.Undefined); + } -// Bool result = rule(Message1); -// Assert.Equal(result, Bool.True); -// } + [Theory, Unit] + [InlineData("$body.tags.Weather.Temperature_PropertyConflict = '100'")] + [InlineData("as_number($body.tags.Weather.Temperature_PropertyConflict) = 100")] + [InlineData("{$body.tags.Weather.Temperature_PropertyConflict} <> 100")] + [InlineData("$body.tags.Weather.Location.State = 'WA'")] + [InlineData("{$body.tags.Weather.Location.State} <> 'CA'")] + public void BodyQuery_SysPropertyConflict(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// [Fact, Unit] -// public void BodyQuery_NotSupported() -// { -// string condition = "$body.tags.Weather.Temperature >= 50"; + Bool result = rule(Message1); + Assert.Equal(result, Bool.True); + } -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.None)); -// } + [Fact, Unit] + public void BodyQuery_NotSupported() + { + string condition = "$body.tags.Weather.Temperature >= 50"; -// [Theory, Unit] -// [InlineData("$body.tags.Weather.Time <> '100'")] -// [InlineData("$body.tags.Weather <> null")] -// public void BodyQuery_NotSupportedJTokenTime(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.None)); + } -// Bool result = rule(Message1); -// Assert.Equal(result, Bool.Undefined); -// } + [Theory, Unit] + [InlineData("$body.tags.Weather.Time <> '100'")] + [InlineData("$body.tags.Weather <> null")] + public void BodyQuery_NotSupportedJTokenTime(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// [Theory, Unit] -// [InlineData("NOT(is_defined($body.tags.Weather.NullValue))")] -// [InlineData("$body.tags.Temperature = null")] -// [InlineData("$body.tags.Weather.Temperature != null")] -// [InlineData("$body.tags.Weather.Location.State <> null")] -// [InlineData("$body.tags.Weather.InvalidProperty = null")] -// [InlineData("NOT ($body.tags.Weather.InvalidProperty != null)")] -// public void BodyQuery_Null(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Bool result = rule(Message1); + Assert.Equal(result, Bool.Undefined); + } -// Bool result = rule(Message1); -// Assert.Equal(result, Bool.True); -// } + [Theory, Unit] + [InlineData("NOT(is_defined($body.tags.Weather.NullValue))")] + [InlineData("$body.tags.Temperature = null")] + [InlineData("$body.tags.Weather.Temperature != null")] + [InlineData("$body.tags.Weather.Location.State <> null")] + [InlineData("$body.tags.Weather.InvalidProperty = null")] + [InlineData("NOT ($body.tags.Weather.InvalidProperty != null)")] + public void BodyQuery_Null(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// [Theory, Unit] -// [InlineData("abs($body.tags.Weather.FreezingTemperature) = 50.4")] -// [InlineData("ceiling(as_number($body.tags.Weather.PreciseTemperature)) = 51")] -// [InlineData("concat($body.tags.Weather.Location.Street, ', ', $body.tags.Weather.Location.City, ', ', $body.tags.Weather.Location.State) = 'One Microsoft Way, Redmond, WA'")] -// [InlineData("contains($body.tags.Weather.Location.Street, 'Microsoft')")] -// [InlineData("ends_with($body.tags.Weather.Location.Street, 'Way')")] -// [InlineData("exp(($body.tags.Weather.Temperature - 49)) < ($body.tags.Weather.Temperature - 47)")] -// [InlineData("index_of($body.tags.Weather.Location.Street, 'Way') > 6")] -// [InlineData("is_defined($body.tags.Weather.Location.Street)")] -// [InlineData("NOT(is_defined($body.tags.Weather.NullValue))")] -// [InlineData("is_string($body.tags.Weather.Location.Street)")] -// [InlineData("length($body.tags.Weather.Location.State) = 2")] -// [InlineData("lower($body.tags.Weather.Location.State) = 'wa'")] -// [InlineData("power($body.tags.Weather.Temperature, 2) = 2500.0")] -// [InlineData("power($body.tags.Weather.Temperature, length($body.tags.Weather.Location.State)) = 2500.0")] -// [InlineData("sign($body.tags.Weather.FreezingTemperature) = -1")] -// [InlineData("square($body.tags.Weather.Temperature) = 2500")] -// [InlineData("power($body.tags.Weather.Temperature, length($body.tags.Weather.Location.State)) = square($body.tags.Weather.Temperature)")] -// [InlineData("starts_with($body.tags.Weather.Location.Street, 'One')")] -// [InlineData("substring($body.tags.Weather.Location.Street, length($body.tags.Weather.Location.Street) - 3) = 'Way'")] -// [InlineData("substring($body.tags.Weather.Location.Street, 4, length('Microsoft')) = 'Microsoft'")] -// [InlineData("upper($body.tags.Weather.Location.Street) = 'ONE MICROSOFT WAY'")] -// public void BodyQuery_Builtins(string condition) -// { -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + Bool result = rule(Message1); + Assert.Equal(result, Bool.True); + } -// Bool result = rule(Message1); -// Assert.Equal(result, Bool.True); -// } + [Theory, Unit] + [InlineData("abs($body.tags.Weather.FreezingTemperature) = 50.4")] + [InlineData("ceiling(as_number($body.tags.Weather.PreciseTemperature)) = 51")] + [InlineData("concat($body.tags.Weather.Location.Street, ', ', $body.tags.Weather.Location.City, ', ', $body.tags.Weather.Location.State) = 'One Microsoft Way, Redmond, WA'")] + [InlineData("contains($body.tags.Weather.Location.Street, 'Microsoft')")] + [InlineData("ends_with($body.tags.Weather.Location.Street, 'Way')")] + [InlineData("exp(($body.tags.Weather.Temperature - 49)) < ($body.tags.Weather.Temperature - 47)")] + [InlineData("index_of($body.tags.Weather.Location.Street, 'Way') > 6")] + [InlineData("is_defined($body.tags.Weather.Location.Street)")] + [InlineData("NOT(is_defined($body.tags.Weather.NullValue))")] + [InlineData("is_string($body.tags.Weather.Location.Street)")] + [InlineData("length($body.tags.Weather.Location.State) = 2")] + [InlineData("lower($body.tags.Weather.Location.State) = 'wa'")] + [InlineData("power($body.tags.Weather.Temperature, 2) = 2500.0")] + [InlineData("power($body.tags.Weather.Temperature, length($body.tags.Weather.Location.State)) = 2500.0")] + [InlineData("sign($body.tags.Weather.FreezingTemperature) = -1")] + [InlineData("square($body.tags.Weather.Temperature) = 2500")] + [InlineData("power($body.tags.Weather.Temperature, length($body.tags.Weather.Location.State)) = square($body.tags.Weather.Temperature)")] + [InlineData("starts_with($body.tags.Weather.Location.Street, 'One')")] + [InlineData("substring($body.tags.Weather.Location.Street, length($body.tags.Weather.Location.Street) - 3) = 'Way'")] + [InlineData("substring($body.tags.Weather.Location.Street, 4, length('Microsoft')) = 'Microsoft'")] + [InlineData("upper($body.tags.Weather.Location.Street) = 'ONE MICROSOFT WAY'")] + public void BodyQuery_Builtins(string condition) + { + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// [Fact, Unit] -// public void DebugBodyQuery() -// { -// string condition = "$BODY.State_0 != '40'"; + Bool result = rule(Message1); + Assert.Equal(result, Bool.True); + } -// var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); + [Fact, Unit] + public void DebugBodyQuery() + { + string condition = "$BODY.State_0 != '40'"; -// try -// { -// Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); + var route = new Route("id", condition, "hub", MessageSource.TwinChangeEvents, new HashSet()); -// Bool result = rule(Message1); + try + { + Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.BodyQuery); -// Assert.NotNull(result); -// Assert.Equal(result, Bool.True); -// } -// catch (Exception ex) -// { -// Assert.NotNull(ex); -// } -// } -// } -//} + Bool result = rule(Message1); + + Assert.NotNull(result); + Assert.Equal(result, Bool.True); + } + catch (Exception ex) + { + Assert.NotNull(ex); + } + } + } +} +*/ diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinChangeIncludesTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinChangeIncludesTest.cs index b53aac5e99d..84042e777e5 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinChangeIncludesTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/TwinChangeIncludesTest.cs @@ -1,19 +1,20 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Routing.Core.Test.query +namespace Microsoft.Azure.Devices.Routing.Core.Test.Query { using System; using System.Collections.Generic; using System.Text; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.MessageSources; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; public class TwinChangeIncludesTest : RoutingUnitTestBase { const string TwinNotificationMessage = "{\"properties\":{\"reported\":{\"WeatherTwin\":{\"Temperature\":50,\"Location\":{\"Street\":\"One Microsoft Way\",\"City\":\"Redmond\",\"State\":\"WA\"}},\"Metadata\":\"metadata\",\"$metadata\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\",\"WeatherTwin\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\",\"Temperature\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\"},\"Location\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\",\"Street\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\"},\"City\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\"},\"State\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\"}}},\"Metadata\":{\"$lastUpdated\":\"2017-02-15T00:42:19.8563501Z\"}},\"$version\":4}}}"; - static readonly IMessage Message1 = new Message(TwinChangeEventMessageSource.Instance, + static readonly IMessage Message1 = new Message( + TwinChangeEventMessageSource.Instance, Encoding.UTF8.GetBytes(TwinNotificationMessage), new Dictionary { @@ -34,7 +35,8 @@ public class TwinChangeIncludesTest : RoutingUnitTestBase { SystemProperties.ContentType, Constants.SystemPropertyValues.ContentType.Json }, }); - [Theory, Unit] + [Theory] + [Unit] [InlineData("twin_change_includes(properties.reported.WeatherTwin)")] [InlineData("TWIN_CHANGE_INCLUDES(properties.reported.WeatherTwin)")] // upper case [InlineData("Twin_Change_Includes(properties.reported.WeatherTwin)")] // Mixed case @@ -54,7 +56,8 @@ public void TestTwinChangeIncludesSuccess(string condition) Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("twin_change_includes(properties.desired.WeatherTwin)")] [InlineData("twin_change_includes(properties.reported.WeatherTwin.Location.City) AND twin_change_includes(properties.reported.WeatherTwin.Temperature123)")] public void TestTwinChangeIncludesFailure(string condition) @@ -64,7 +67,8 @@ public void TestTwinChangeIncludesFailure(string condition) Assert.Equal(rule(Message1), Bool.False); } - [Fact, Unit] + [Fact] + [Unit] public void TestTwinChangeIncludes_Debug() { string condition = "twin_change_includes(properties.reported.WeatherTwin)"; @@ -74,7 +78,8 @@ public void TestTwinChangeIncludes_Debug() Assert.Equal(rule(Message1), Bool.True); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("twin_change_includes()")] [InlineData("twin_change_includes( )")] [InlineData("twin_change_includes(properties.)")] @@ -97,7 +102,8 @@ public void TestTwinChangeIncludesCompilationFailure(string condition) Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.TwinChangeIncludes)); } - [Fact, Unit] + [Fact] + [Unit] public void TestTwinChangeIncludes_NotSupported_RouteCompilerFlag() { string condition = "twin_change_includes(properties.reported.WeatherTwin)"; @@ -105,7 +111,8 @@ public void TestTwinChangeIncludes_NotSupported_RouteCompilerFlag() Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.None)); } - [Fact, Unit] + [Fact] + [Unit] public void TestTwinChangeIncludes_NotSupported_MessageSource() { string condition = "twin_change_includes(properties.reportedWeatherTwin)"; @@ -113,7 +120,8 @@ public void TestTwinChangeIncludes_NotSupported_MessageSource() Assert.Throws(() => RouteCompiler.Instance.Compile(route, RouteCompilerFlags.TwinChangeIncludes)); } - [Theory, Unit] + [Theory] + [Unit] [InlineData("")] [InlineData("random message")] public void TestTwinChangeIncludes_ValidateMessage(string messageBody) @@ -122,7 +130,8 @@ public void TestTwinChangeIncludes_ValidateMessage(string messageBody) var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.TwinChangeIncludes); - var invalidMessage = new Message(TwinChangeEventMessageSource.Instance, + var invalidMessage = new Message( + TwinChangeEventMessageSource.Instance, Encoding.UTF8.GetBytes(messageBody), new Dictionary(), new Dictionary @@ -134,14 +143,16 @@ public void TestTwinChangeIncludes_ValidateMessage(string messageBody) Assert.Equal(rule(invalidMessage), Bool.Undefined); } - [Fact, Unit] + [Fact] + [Unit] public void TestTwinChangeIncludes_InvalidEncoding() { string condition = "twin_change_includes(properties.reported.WeatherTwin)"; var route = new Route("id", condition, "hub", TwinChangeEventMessageSource.Instance, new HashSet()); Func rule = RouteCompiler.Instance.Compile(route, RouteCompilerFlags.TwinChangeIncludes); - var invalidMessage = new Message(TwinChangeEventMessageSource.Instance, + var invalidMessage = new Message( + TwinChangeEventMessageSource.Instance, Encoding.Unicode.GetBytes(TwinNotificationMessage), new Dictionary(), new Dictionary diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/UndefinedTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/UndefinedTest.cs index f7ae400c23c..94f950d1dd8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/UndefinedTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/UndefinedTest.cs @@ -4,14 +4,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; - using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query; using Xunit; [ExcludeFromCodeCoverage] public class UndefinedTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void SmokeTest() { BinaryExpression expression = Expression.LessThan(Expression.Add(Expression.Constant(3.0, typeof(double)), Expression.Constant(Undefined.Instance)), Expression.Constant(4.0)); @@ -19,7 +20,8 @@ public void SmokeTest() Assert.False(rule()); } - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { var d1 = new Undefined(); @@ -33,14 +35,16 @@ public void TestEquals() Assert.Equal(Bool.Undefined, d1 == Bool.False); } - [Fact, Unit] + [Fact] + [Unit] public void TestTypes() { Assert.Equal(Bool.True, Undefined.IsDefined("undefined")); Assert.Equal(Bool.False, Undefined.IsDefined((string)Undefined.Instance)); } - [Fact, Unit] + [Fact] + [Unit] public void TestArthimetic() { var d1 = new Undefined(); @@ -63,7 +67,8 @@ public void TestArthimetic() Assert.Equal(double.NaN, d1 / d2); } - [Fact, Unit] + [Fact] + [Unit] public void TestComparison() { var d1 = new Undefined(); @@ -86,7 +91,8 @@ public void TestComparison() Assert.Equal(Bool.Undefined, d1 >= d2); } - [Fact, Unit] + [Fact] + [Unit] public void TestExpression() { // Add diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/CompilationErrorTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/CompilationErrorTest.cs index dbf875b186a..823bae039d3 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/CompilationErrorTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/CompilationErrorTest.cs @@ -3,14 +3,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query.Errors { using System; using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Xunit; [ExcludeFromCodeCoverage] public class CompilationErrorTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void SmokeTest() { var start = new ErrorPosition(2, 2); @@ -26,7 +27,8 @@ public void SmokeTest() Assert.Throws(() => new CompilationError(ErrorSeverity.Error, "message", new ErrorRange(end, start))); } - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { var error1 = new CompilationError(ErrorSeverity.Error, "message", new ErrorRange(new ErrorPosition(1, 1), new ErrorPosition(1, 2))); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorPositionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorPositionTest.cs index aca0afede72..a5dcd2d20c4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorPositionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorPositionTest.cs @@ -2,14 +2,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query.Errors { using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Xunit; [ExcludeFromCodeCoverage] public class ErrorPositionTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { var pos1 = new ErrorPosition(1, 1); @@ -31,7 +32,8 @@ public void TestEquals() Assert.NotEqual(pos1.GetHashCode(), pos2.GetHashCode()); } - [Fact, Unit] + [Fact] + [Unit] public void TestCompareTo() { var pos1 = new ErrorPosition(1, 1); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorRangeTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorRangeTest.cs index 9a9af1ba8c6..0cb764cdf19 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorRangeTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/query/errors/ErrorRangeTest.cs @@ -2,14 +2,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Query.Errors { using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Query.Errors; using Xunit; [ExcludeFromCodeCoverage] public class ErrorRangeTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { var range1 = new ErrorRange(new ErrorPosition(1, 1), new ErrorPosition(1, 3)); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/RouteFactoryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/RouteFactoryTest.cs index 7a12d65cda0..2efdb4f04df 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/RouteFactoryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/RouteFactoryTest.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.RouteFactory using System; using System.Collections.Generic; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Microsoft.Azure.Devices.Routing.Core.Query; using Microsoft.Azure.Devices.Routing.Core.Test.Endpoints; @@ -14,180 +13,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.RouteFactory [Unit] public class RouteFactoryTest { - static IEnumerable GetFunctionEndpointParserData() - { - var testData = new List(); - testData.Add(new object[] - { - @"FROM /* INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")", - CustomMessageSource.Create("/"), - "true", - "brokeredEndpoint", - "/modules/alertLogic/inputs/in" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules/adapter INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")", - CustomMessageSource.Create("/messages/modules/adapter"), - "true", - "brokeredEndpoint", - "/modules/alertLogic/inputs/in" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules/alertLogic/outputs/commands INTO brokeredEndpoint(""/modules/adapter/inputs/write"")", - CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), - @"true", - "brokeredEndpoint", - "/modules/adapter/inputs/write" - }); - - testData.Add(new object[] - { - @"FROM /messages INTO brokeredEndpoint(""/modules/alertLogic/inputs/in1"")", - CustomMessageSource.Create("/messages"), - "true", - "brokeredEndpoint", - "/modules/alertLogic/inputs/in1" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules WHERE messageType = ""alert"" INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")", - CustomMessageSource.Create("/messages/modules/"), - @"messageType = ""alert""", - "brokeredEndpoint", - "/modules/alertLogic/inputs/in" - }); - - testData.Add(new object[] - { - @" FROM /messages/modules/adapter WHERE messageType = ""alert"" AND alert = ""imp"" INTO brokeredEndpoint(""/a/b/c/d"") ", - CustomMessageSource.Create("/messages/modules/adapter"), - @"messageType = ""alert"" AND alert = ""imp""", - "brokeredEndpoint", - "/a/b/c/d" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules/alertLogic/outputs/commands WHERE messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"") INTO brokeredEndpoint(""/modules/adapter/inputs/write"")", - CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), - @"messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"")", - "brokeredEndpoint", - "/modules/adapter/inputs/write" - }); - - testData.Add(new object[] - { - @" FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO brokeredEndpoint(""/modules/alertLogic/inputs/in1"")", - CustomMessageSource.Create("/messages"), - @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", - "brokeredEndpoint", - "/modules/alertLogic/inputs/in1" - }); - - testData.Add(new object[] - { - @" SELECT * FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO brokeredEndpoint(""/modules/alertLogic/inputs/in1"")", - CustomMessageSource.Create("/messages"), - @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", - "brokeredEndpoint", - "/modules/alertLogic/inputs/in1" - }); - - return testData; - } - - static IEnumerable GetSystemEndpointParserData() - { - var testData = new List(); - - testData.Add(new object[] - { - @"FROM /* INTO $upstream", - CustomMessageSource.Create("/"), - "true", - "$upstream" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules/adapter INTO $upstream", - CustomMessageSource.Create("/messages/modules/adapter"), - "true", - "$upstream" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules/alertLogic/outputs/commands INTO $upstream", - CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), - @"true", - "$upstream" - }); - - testData.Add(new object[] - { - @"FROM /messages INTO $upstream", - CustomMessageSource.Create("/messages"), - "true", - "$upstream" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules WHERE messageType = ""alert"" INTO $upstream", - CustomMessageSource.Create("/messages/modules"), - @"messageType = ""alert""", - "$upstream" - }); - - testData.Add(new object[] - { - @" FROM /messages/modules/adapter WHERE messageType = ""alert"" AND alert = ""imp"" INTO $upstream ", - CustomMessageSource.Create("/messages/modules/adapter"), - @"messageType = ""alert"" AND alert = ""imp""", - "$upstream" - }); - - testData.Add(new object[] - { - @"FROM /messages/modules/alertLogic/outputs/commands WHERE messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"") INTO $upstream", - CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), - @"messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"")", - "$upstream" - }); - - testData.Add(new object[] - { - @" FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO $upstream", - CustomMessageSource.Create("/messages"), - @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", - "$upstream" - }); - - testData.Add(new object[] - { - @" SELECT * FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO $upstream", - CustomMessageSource.Create("/messages"), - @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", - "$upstream" - }); - - return testData; - } - [MemberData(nameof(GetFunctionEndpointParserData))] [Theory] public void TestParseRouteWithFunctionEndpoint(string routeString, IMessageSource expectedSource, string expectedCondition, string function, string inputEndpoint) { var mockEndpointFactory = new Mock(); - mockEndpointFactory.Setup(ef => ef.CreateFunctionEndpoint( - It.Is(s => s.Equals(function, StringComparison.OrdinalIgnoreCase)), - It.Is(s => s.Equals(inputEndpoint, StringComparison.OrdinalIgnoreCase)))) + mockEndpointFactory.Setup( + ef => ef.CreateFunctionEndpoint( + It.Is(s => s.Equals(function, StringComparison.OrdinalIgnoreCase)), + It.Is(s => s.Equals(inputEndpoint, StringComparison.OrdinalIgnoreCase)))) .Returns(new TestEndpoint(function)); var routeFactory = new TestRouteFactory(mockEndpointFactory.Object); @@ -208,8 +42,9 @@ public void TestParseRouteWithFunctionEndpoint(string routeString, IMessageSourc public void TestParseRouteWithSystemEndpoint(string routeString, IMessageSource expectedSource, string expectedCondition, string systemEndpoint) { var mockEndpointFactory = new Mock(); - mockEndpointFactory.Setup(ef => ef.CreateSystemEndpoint( - It.Is(s => s.Equals(systemEndpoint, StringComparison.OrdinalIgnoreCase)))) + mockEndpointFactory.Setup( + ef => ef.CreateSystemEndpoint( + It.Is(s => s.Equals(systemEndpoint, StringComparison.OrdinalIgnoreCase)))) .Returns(new TestEndpoint(systemEndpoint)); var routeFactory = new TestRouteFactory(mockEndpointFactory.Object); @@ -224,14 +59,14 @@ public void TestParseRouteWithSystemEndpoint(string routeString, IMessageSource Assert.NotNull(endpoint); Assert.Equal(systemEndpoint, endpoint.Name); } - + [Theory] [InlineData(@"FORM /messages/modules/adapter INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")")] [InlineData(@"SELECT FROM /messages/modules/adapter INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")")] [InlineData(@"FROM INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")")] [InlineData(@"FROM /messages WHERE temp == 100 INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")")] [InlineData(@"FROM /messages WHERE INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")")] - [InlineData(@"FROM /messages WHERE temp = 'high' INTO")] + [InlineData(@"FROM /messages WHERE temp = 'high' INTO")] [InlineData(@"FROM /messages INTO brokeredEndpoint(""/modules/alertLogic/inputs/in""")] [InlineData(@"FROM /messages INTO brokeredEndpoint(""/modules/alertLogic/inputs/in)")] [InlineData(@"FROM /messages INTO brokeredEndpoint ""/modules/alertLogic/inputs/in"")")] @@ -248,5 +83,189 @@ public void TestInvalidRoutes(string routeString) Assert.Throws(typeof(RouteCompilationException), () => routeFactory.Create(routeString)); } + + static IEnumerable GetFunctionEndpointParserData() + { + var testData = new List(); + testData.Add( + new object[] + { + @"FROM /* INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")", + CustomMessageSource.Create("/"), + "true", + "brokeredEndpoint", + "/modules/alertLogic/inputs/in" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules/adapter INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")", + CustomMessageSource.Create("/messages/modules/adapter"), + "true", + "brokeredEndpoint", + "/modules/alertLogic/inputs/in" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules/alertLogic/outputs/commands INTO brokeredEndpoint(""/modules/adapter/inputs/write"")", + CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), + @"true", + "brokeredEndpoint", + "/modules/adapter/inputs/write" + }); + + testData.Add( + new object[] + { + @"FROM /messages INTO brokeredEndpoint(""/modules/alertLogic/inputs/in1"")", + CustomMessageSource.Create("/messages"), + "true", + "brokeredEndpoint", + "/modules/alertLogic/inputs/in1" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules WHERE messageType = ""alert"" INTO brokeredEndpoint(""/modules/alertLogic/inputs/in"")", + CustomMessageSource.Create("/messages/modules/"), + @"messageType = ""alert""", + "brokeredEndpoint", + "/modules/alertLogic/inputs/in" + }); + + testData.Add( + new object[] + { + @" FROM /messages/modules/adapter WHERE messageType = ""alert"" AND alert = ""imp"" INTO brokeredEndpoint(""/a/b/c/d"") ", + CustomMessageSource.Create("/messages/modules/adapter"), + @"messageType = ""alert"" AND alert = ""imp""", + "brokeredEndpoint", + "/a/b/c/d" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules/alertLogic/outputs/commands WHERE messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"") INTO brokeredEndpoint(""/modules/adapter/inputs/write"")", + CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), + @"messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"")", + "brokeredEndpoint", + "/modules/adapter/inputs/write" + }); + + testData.Add( + new object[] + { + @" FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO brokeredEndpoint(""/modules/alertLogic/inputs/in1"")", + CustomMessageSource.Create("/messages"), + @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", + "brokeredEndpoint", + "/modules/alertLogic/inputs/in1" + }); + + testData.Add( + new object[] + { + @" SELECT * FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO brokeredEndpoint(""/modules/alertLogic/inputs/in1"")", + CustomMessageSource.Create("/messages"), + @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", + "brokeredEndpoint", + "/modules/alertLogic/inputs/in1" + }); + + return testData; + } + + static IEnumerable GetSystemEndpointParserData() + { + var testData = new List(); + + testData.Add( + new object[] + { + @"FROM /* INTO $upstream", + CustomMessageSource.Create("/"), + "true", + "$upstream" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules/adapter INTO $upstream", + CustomMessageSource.Create("/messages/modules/adapter"), + "true", + "$upstream" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules/alertLogic/outputs/commands INTO $upstream", + CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), + @"true", + "$upstream" + }); + + testData.Add( + new object[] + { + @"FROM /messages INTO $upstream", + CustomMessageSource.Create("/messages"), + "true", + "$upstream" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules WHERE messageType = ""alert"" INTO $upstream", + CustomMessageSource.Create("/messages/modules"), + @"messageType = ""alert""", + "$upstream" + }); + + testData.Add( + new object[] + { + @" FROM /messages/modules/adapter WHERE messageType = ""alert"" AND alert = ""imp"" INTO $upstream ", + CustomMessageSource.Create("/messages/modules/adapter"), + @"messageType = ""alert"" AND alert = ""imp""", + "$upstream" + }); + + testData.Add( + new object[] + { + @"FROM /messages/modules/alertLogic/outputs/commands WHERE messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"") INTO $upstream", + CustomMessageSource.Create("/messages/modules/alertLogic/outputs/commands"), + @"messageType = ""alert"" AND ( temp > 100 OR humidity = 50 OR place = ""basement"")", + "$upstream" + }); + + testData.Add( + new object[] + { + @" FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO $upstream", + CustomMessageSource.Create("/messages"), + @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", + "$upstream" + }); + + testData.Add( + new object[] + { + @" SELECT * FROM /messages WHERE messageType = ""alert"" AND humidity = 'high' AND temp >= 200 INTO $upstream", + CustomMessageSource.Create("/messages"), + @"messageType = ""alert"" AND humidity = 'high' AND temp >= 200", + "$upstream" + }); + + return testData; + } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/TestRouteFactory.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/TestRouteFactory.cs index 7bf0c36f87e..a701072bd96 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/TestRouteFactory.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/routeFactory/TestRouteFactory.cs @@ -2,7 +2,7 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.RouteFactory { using System; - using Microsoft.Azure.Devices.Routing.Core; + using RouteFactory = Microsoft.Azure.Devices.Routing.Core.RouteFactory; public class TestRouteFactory : RouteFactory { diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FilteringRoutingServiceTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FilteringRoutingServiceTest.cs index c236d283b72..31f68ef2ad4 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FilteringRoutingServiceTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FilteringRoutingServiceTest.cs @@ -6,11 +6,11 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Services using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.Endpoints; + using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Microsoft.Azure.Devices.Routing.Core.Services; using Microsoft.Azure.Devices.Routing.Core.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Moq; using Xunit; @@ -30,13 +30,15 @@ public class FilteringRoutingServiceTest static FilteringRoutingServiceTest() { - RouteStore = new RouteStore(new Dictionary - { - { "hub1", new RouterConfig(AllEndpoints, AllRoutes)} - }); + RouteStore = new RouteStore( + new Dictionary + { + { "hub1", new RouterConfig(AllEndpoints, AllRoutes) } + }); } - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var underlying = new Mock(); @@ -58,13 +60,15 @@ public async Task SmokeTest() underlying.Verify(s => s.RouteAsync("hub2", It.IsAny>()), Times.Never); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestRules() { - var store = new RouteStore(new Dictionary - { - { "hub1", new RouterConfig(AllEndpoints, new List { Route1 }) } - }); + var store = new RouteStore( + new Dictionary + { + { "hub1", new RouterConfig(AllEndpoints, new List { Route1 }) } + }); var underlying = new Mock(); var client = new FilteringRoutingService(underlying.Object, store, NullNotifierFactory.Instance); @@ -72,7 +76,8 @@ public async Task TestRules() underlying.Verify(s => s.RouteAsync("hub1", new[] { Message1 }), Times.Once); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClose() { var underlying = new Mock(); @@ -89,7 +94,8 @@ public async Task TestClose() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestChangingHub() { var notifier = new TestNotifier(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FrontendRoutingServiceTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FrontendRoutingServiceTest.cs index 8c351b5196a..1075a17968d 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FrontendRoutingServiceTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/services/FrontendRoutingServiceTest.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Services using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Azure.Devices.Routing.Core.MessageSources; using Microsoft.Azure.Devices.Routing.Core.Services; using Microsoft.Azure.Devices.Routing.Core.Test.Sinks; @@ -16,10 +15,10 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Services [Unit] public class FrontendRoutingServiceTest { - static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] {2, 3, 1}, new Dictionary { {"key1", "value1"}, {"key2", "value2"} }); - static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] {1, 2, 3}, new Dictionary { {"key", "value"}, {"key2", "value2"} }); + static readonly IMessage Message1 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message2 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message3 = new Message(TelemetryMessageSource.Instance, new byte[] { 2, 3, 1 }, new Dictionary { { "key1", "value1" }, { "key2", "value2" } }); + static readonly IMessage Message4 = new Message(TelemetryMessageSource.Instance, new byte[] { 1, 2, 3 }, new Dictionary { { "key", "value" }, { "key2", "value2" } }); [Fact] public async Task SmokeTest() diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSink.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSink.cs index 5efd98c3d36..e6d6fa79fe0 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSink.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSink.cs @@ -11,13 +11,13 @@ public class FailedSink : ISink { static readonly IList Empty = new List(); - public Exception Exception { get; } - public FailedSink(Exception exception) { this.Exception = exception; } + public Exception Exception { get; } + public Task> ProcessAsync(T t, CancellationToken token) => this.ProcessAsync(new[] { t }, token); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSinkFactory.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSinkFactory.cs index 2425b57a1e1..35badabf8f3 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSinkFactory.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/FailedSinkFactory.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks { using System; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core; using Microsoft.Azure.Devices.Routing.Core.Util; public class FailedSinkFactory : ISinkFactory diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkFactoryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkFactoryTest.cs index 986919e87ef..15b092a1b4f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkFactoryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkFactoryTest.cs @@ -2,14 +2,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks { using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Sinks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Sinks; using Xunit; [ExcludeFromCodeCoverage] public class NullSinkFactoryTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void SmokeTest() { var factory = new NullSinkFactory(); @@ -17,4 +18,4 @@ public void SmokeTest() Assert.IsType>(sink); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkTest.cs index 9cd39553119..47efa88a261 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/NullSinkTest.cs @@ -4,25 +4,26 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Sinks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Sinks; using Xunit; [ExcludeFromCodeCoverage] public class NullSinkTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public void SmokeTask() { var sink = new NullSink(); Task result = sink.ProcessAsync(1, CancellationToken.None); Assert.True(result.IsCompleted); - Task result2 = sink.ProcessAsync(new[] { 1, 2}, CancellationToken.None); + Task result2 = sink.ProcessAsync(new[] { 1, 2 }, CancellationToken.None); Assert.True(result2.IsCompleted); Task result3 = sink.CloseAsync(CancellationToken.None); Assert.True(result3.IsCompleted); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/PartialFailureSink.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/PartialFailureSink.cs index b44009ed39a..89a2933526f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/PartialFailureSink.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/PartialFailureSink.cs @@ -10,13 +10,13 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks public class PartialFailureSink : ISink { - public Exception Exception { get; } - public PartialFailureSink(Exception exception) { this.Exception = exception; } + public Exception Exception { get; } + public Task> ProcessAsync(T t, CancellationToken token) => this.ProcessAsync(new[] { t }, token); @@ -34,6 +34,7 @@ public Task> ProcessAsync(ICollection ts, CancellationToken to T[] failed = ts.Skip(ts.Count / 2).ToArray(); result = new SinkResult(successful, failed, new SendFailureDetails(FailureKind.InternalError, this.Exception)); } + return Task.FromResult(result); } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/RetryingSinkTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/RetryingSinkTest.cs index b74d659a5c2..a380f716923 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/RetryingSinkTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/RetryingSinkTest.cs @@ -15,7 +15,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks public class RetryingSinkTest { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTask() { var factory = new RetryingSinkFactory(new TestSinkFactory(), RetryPolicy.NoRetry); @@ -30,7 +31,8 @@ public async Task SmokeTask() await sink.CloseAsync(CancellationToken.None); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestSendCompletes() { var retryPolicy = new RetryPolicy(new ErrorDetectionStrategy(_ => true), new FixedInterval(3, TimeSpan.FromMilliseconds(10))); @@ -44,7 +46,8 @@ public async Task TestSendCompletes() Assert.Equal(new List(items), result.Succeeded); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestFailure() { var retryPolicy = new RetryPolicy(new ErrorDetectionStrategy(_ => true), new FixedInterval(2, TimeSpan.FromMilliseconds(10))); @@ -59,7 +62,8 @@ public async Task TestFailure() Assert.Equal(new List>(), result.InvalidDetailsList); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCancellation() { var cts = new CancellationTokenSource(); @@ -81,7 +85,8 @@ public async Task TestCancellation() result.SendFailureDetails.ForEach(sfd => Assert.IsType(sfd.RawException)); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestNonTransient() { var retryPolicy = new RetryPolicy(new ErrorDetectionStrategy(_ => false), new FixedInterval(int.MaxValue, TimeSpan.FromMilliseconds(10))); @@ -104,7 +109,8 @@ public async Task TestNonTransient() } } - [Fact, Unit] + [Fact] + [Unit] public async Task TestClose() { var underlying = new Mock>(); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSink.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSink.cs index 5da829f4819..14754081ede 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSink.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSink.cs @@ -8,16 +8,16 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks public class TestSink : ISink { - public IList Processed { get; } - - public bool IsClosed { get; private set; } - public TestSink() { this.Processed = new List(); this.IsClosed = false; } + public IList Processed { get; } + + public bool IsClosed { get; private set; } + public Task> ProcessAsync(T t, CancellationToken ct) { this.Processed.Add(t); @@ -31,6 +31,7 @@ public Task> ProcessAsync(ICollection ts, CancellationToken ct { this.Processed.Add(t); } + ISinkResult result = new SinkResult(ts); return Task.FromResult(result); } @@ -41,4 +42,4 @@ public Task CloseAsync(CancellationToken token) return TaskEx.Done; } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSinkFactory.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSinkFactory.cs index 5456c5f036b..8d8802c8f8f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSinkFactory.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sinks/TestSinkFactory.cs @@ -6,8 +6,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Sinks public class TestSinkFactory : ISinkFactory { - volatile ImmutableDictionary> sinks = ImmutableDictionary>.Empty; readonly object sync = new object(); + volatile ImmutableDictionary> sinks = ImmutableDictionary>.Empty; public Task> CreateAsync(string hubName) { @@ -20,7 +20,8 @@ public Task> CreateAsync(string hubName) this.sinks = this.sinks.Add(hubName, sink); } } + return Task.FromResult>(sink); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceFactoryTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceFactoryTest.cs index e07cb29699f..2995faacc91 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceFactoryTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceFactoryTest.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Routing.Core.Test.sources +namespace Microsoft.Azure.Devices.Routing.Core.Test.Sources { using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Sources; using Microsoft.Azure.Devices.Routing.Core.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; [ExcludeFromCodeCoverage] @@ -16,7 +16,8 @@ public class NullSourceFactoryTest : RoutingUnitTestBase { static readonly IEndpointExecutorFactory ExecutorFactory = new AsyncEndpointExecutorFactory(TestConstants.DefaultConfig, TestConstants.DefaultOptions); - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { Router router = await Router.CreateAsync("router1", "hub", new RouterConfig(new HashSet(), new HashSet(), Option.None()), ExecutorFactory); @@ -25,4 +26,4 @@ public async Task SmokeTest() Assert.IsType(source); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceTest.cs index 9e4e27f0bd0..5135d714242 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/NullSourceTest.cs @@ -1,20 +1,21 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Routing.Core.Test.sources +namespace Microsoft.Azure.Devices.Routing.Core.Test.Sources { using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Routing.Core.Endpoints; using Microsoft.Azure.Devices.Routing.Core.Sources; using Microsoft.Azure.Devices.Routing.Core.Util; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; [ExcludeFromCodeCoverage] public class NullSourceTest : RoutingUnitTestBase { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { var executorFactory = new AsyncEndpointExecutorFactory(TestConstants.DefaultConfig, TestConstants.DefaultOptions); @@ -24,4 +25,4 @@ public async Task SmokeTest() Assert.True(result.IsCompleted); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/TestSource.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/TestSource.cs index 17c4dc19861..cabc33eb877 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/TestSource.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/sources/TestSource.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Routing.Core.Test.sources +namespace Microsoft.Azure.Devices.Routing.Core.Test.Sources { using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -13,8 +13,6 @@ public class TestSource : Source readonly AtomicBoolean closed; readonly CancellationTokenSource cts; - public bool Closed => this.closed; - public TestSource(Router router) : base(router) { @@ -22,6 +20,8 @@ public TestSource(Router router) this.cts = new CancellationTokenSource(); } + public bool Closed => this.closed; + public Task SendAsync(IMessage[] messages) => this.closed ? TaskEx.Done : this.Router.RouteAsync(messages); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/CollectionExTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/CollectionExTest.cs index 09398e1a4d0..be98635d2f2 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/CollectionExTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/CollectionExTest.cs @@ -3,13 +3,14 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util { using System.Collections.Generic; using System.Linq; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; public class CollectionExTest { - [Fact, Unit] + [Fact] + [Unit] public void TestGetOrElse() { var dict = new Dictionary @@ -22,10 +23,11 @@ public void TestGetOrElse() Assert.Equal("default", dict.GetOrElse(3, "default")); } - [Fact, Unit] + [Fact] + [Unit] public void TestHeadOption() { - IEnumerable list1 = new [] { 1, 2, 3}; + IEnumerable list1 = new[] { 1, 2, 3 }; IList list2 = new List { 1 }; // ReSharper disable once CollectionNeverUpdated.Local IList list3 = new List(); @@ -37,7 +39,8 @@ public void TestHeadOption() Assert.Equal(Option.None(), Enumerable.Repeat(4, 0).HeadOption()); } - [Fact, Unit] + [Fact] + [Unit] public void TestGet() { var dict = new Dictionary { { "key", "value" } }; diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/DateTimeExTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/DateTimeExTest.cs index 9b40dc6963f..38b10e316f8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/DateTimeExTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/DateTimeExTest.cs @@ -3,17 +3,26 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util { using System; using System.Collections.Generic; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; public class DateTimeExTest { + [Theory] + [Unit] + [MemberData(nameof(TestDataSource.TestData), MemberType = typeof(TestDataSource))] + public void TestSafeAdd(DateTime start, TimeSpan period, DateTime expected) + { + DateTime result = start.SafeAdd(period); + Assert.Equal(expected, result); + } + static class TestDataSource { static readonly IList Data = new List { - new object[] { new DateTime(2016, 3, 27), TimeSpan.FromDays( 1), new DateTime(2016, 3, 28) }, + new object[] { new DateTime(2016, 3, 27), TimeSpan.FromDays(1), new DateTime(2016, 3, 28) }, new object[] { new DateTime(2016, 3, 27), TimeSpan.FromDays(-1), new DateTime(2016, 3, 26) }, new object[] { new DateTime(2016, 3, 27), TimeSpan.MaxValue, DateTime.MaxValue }, new object[] { new DateTime(2016, 3, 27), TimeSpan.MinValue, DateTime.MinValue }, @@ -25,13 +34,5 @@ static class TestDataSource public static IEnumerable TestData => Data; } - - [Theory, Unit] - [MemberData(nameof(TestDataSource.TestData), MemberType = typeof(TestDataSource))] - public void TestSafeAdd(DateTime start, TimeSpan period, DateTime expected) - { - DateTime result = start.SafeAdd(period); - Assert.Equal(expected, result); - } } } diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/MockSystemTime.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/MockSystemTime.cs index 2c01f9dc421..f93f7a8ac2f 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/MockSystemTime.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/MockSystemTime.cs @@ -9,8 +9,6 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util public class MockSystemTime : ISystemTime { - public DateTime UtcNow { get; set; } - public MockSystemTime() : this(DateTime.UtcNow) { @@ -21,6 +19,8 @@ public MockSystemTime(DateTime now) this.UtcNow = now; } + public DateTime UtcNow { get; set; } + public void Add(TimeSpan timespan) { this.UtcNow = this.UtcNow.SafeAdd(timespan); @@ -41,4 +41,4 @@ public void SmokeTest() Assert.Equal(new DateTime(2017, 5, 23, 0, 15, 0, DateTimeKind.Utc), time.UtcNow); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/OptionTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/OptionTest.cs index 21a221d64f5..7999d3cd5c8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/OptionTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/OptionTest.cs @@ -3,14 +3,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util { using System.Diagnostics.CodeAnalysis; using System.Linq; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; [ExcludeFromCodeCoverage] public class OptionTest { - [Fact, Unit] + [Fact] + [Unit] public void TestCreate() { Option some = Option.Some(2); @@ -20,7 +21,8 @@ public void TestCreate() Assert.False(none.HasValue); } - [Fact, Unit] + [Fact] + [Unit] public void TestEquals() { Option some1 = Option.Some(1); @@ -45,7 +47,8 @@ public void TestEquals() Assert.Equal(none1, none2); } - [Fact, Unit] + [Fact] + [Unit] public void TestHashCode() { Option some1 = Option.Some(1); @@ -58,7 +61,8 @@ public void TestHashCode() Assert.NotEqual(some1.GetHashCode(), some2.GetHashCode()); } - [Fact, Unit] + [Fact] + [Unit] public void TestToString() { Option some1 = Option.Some(1); @@ -70,7 +74,8 @@ public void TestToString() Assert.Equal("None", none.ToString()); } - [Fact, Unit] + [Fact] + [Unit] public void TestEnumerable() { Option some = Option.Some(6); @@ -81,6 +86,7 @@ public void TestEnumerable() { i += value; } + Assert.Equal(6, i); int count = 0; @@ -89,13 +95,15 @@ public void TestEnumerable() { count++; } + Assert.Equal(0, count); Assert.Equal(1, some.ToEnumerable().Count()); Assert.Equal(0, none.ToEnumerable().Count()); } - [Fact, Unit] + [Fact] + [Unit] public void TestContains() { Option some = Option.Some(3); @@ -108,7 +116,8 @@ public void TestContains() Assert.True(some2.Contains(null)); } - [Fact, Unit] + [Fact] + [Unit] public void TestExists() { Option some = Option.Some(3); @@ -119,7 +128,8 @@ public void TestExists() Assert.False(none.Exists(_ => true)); } - [Fact, Unit] + [Fact] + [Unit] public void TestGetOrElse() { Option some = Option.Some(3); @@ -129,7 +139,8 @@ public void TestGetOrElse() Assert.Equal(2, none.GetOrElse(2)); } - [Fact, Unit] + [Fact] + [Unit] public void TestElse() { Option some = Option.Some(3); @@ -139,7 +150,8 @@ public void TestElse() Assert.Equal(Option.Some(2), none.Else(Option.Some(2))); } - [Fact, Unit] + [Fact] + [Unit] public void TestOrDefault() { Option some = Option.Some(3); @@ -151,7 +163,8 @@ public void TestOrDefault() Assert.Equal(null, none2.OrDefault()); } - [Fact, Unit] + [Fact] + [Unit] public void TestMap() { Option some = Option.Some(3); @@ -161,7 +174,8 @@ public void TestMap() Assert.Equal(Option.None(), none.Map(v => v * 2)); } - [Fact, Unit] + [Fact] + [Unit] public void TestFlatMap() { Option some = Option.Some(3); @@ -171,7 +185,8 @@ public void TestFlatMap() Assert.Equal(Option.None(), none.FlatMap(v => Option.Some(v * 2))); } - [Fact, Unit] + [Fact] + [Unit] public void TestFilter() { Option some = Option.Some(3); @@ -182,7 +197,8 @@ public void TestFilter() Assert.Equal(Option.None(), none.Filter(_ => false)); } - [Fact, Unit] + [Fact] + [Unit] public void TestForEach() { Option some = Option.Some(3); diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/PreconditionsTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/PreconditionsTest.cs index 0ed520dd316..ea0da1b20ab 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/PreconditionsTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/PreconditionsTest.cs @@ -3,14 +3,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util { using System; using System.Diagnostics.CodeAnalysis; - using Microsoft.Azure.Devices.Routing.Core.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util; using Xunit; [ExcludeFromCodeCoverage] public class PreconditionsTest { - [Fact, Unit] + [Fact] + [Unit] public void TestCheckNotNull() { Assert.Throws(typeof(ArgumentNullException), () => Preconditions.CheckNotNull(null, "param")); @@ -27,7 +28,8 @@ public void TestCheckNotNull() Assert.Equal("my message", message); } - [Fact, Unit] + [Fact] + [Unit] public void TestCheckArgument() { Assert.Throws(typeof(ArgumentException), () => Preconditions.CheckArgument(false)); @@ -35,7 +37,8 @@ public void TestCheckArgument() Preconditions.CheckArgument(true); } - [Fact, Unit] + [Fact] + [Unit] public void TestCheckRange() { int item = Preconditions.CheckRange(6, 5); @@ -50,4 +53,4 @@ public void TestCheckRange() Assert.Throws(() => Preconditions.CheckRange(8, 6, 8)); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/TaskExTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/TaskExTest.cs index 4062f7a740f..06bff02bdc8 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/TaskExTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/TaskExTest.cs @@ -10,7 +10,8 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util public class TaskExTest { - [Fact, Unit] + [Fact] + [Unit] public async Task SmokeTest() { Task t1 = TaskEx.FromException(new ApplicationException("the message")); @@ -24,7 +25,8 @@ public async Task SmokeTest() Assert.Equal("there's a bad image", e2.Message); } - [Fact, Unit] + [Fact] + [Unit] public async Task WhenCancelled() { var cts = new CancellationTokenSource(); @@ -32,4 +34,4 @@ public async Task WhenCancelled() await cts.Token.WhenCanceled(); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AsyncLockTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AsyncLockTest.cs index 1f3deae9200..3b2bed0d830 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AsyncLockTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AsyncLockTest.cs @@ -4,14 +4,15 @@ namespace Microsoft.Azure.Devices.Routing.Core.Test.Util.Concurrency using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Xunit; [ExcludeFromCodeCoverage] public class AsyncLockTest { - [Fact, Unit] + [Fact] + [Unit] public void TestUnlockedPermitsLockSynchronously() { var mutex = new AsyncLock(); @@ -22,21 +23,23 @@ public void TestUnlockedPermitsLockSynchronously() Assert.False(lockTask.IsCanceled); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestLockedPreventsLockUntilUnlocked() { var mutex = new AsyncLock(); var locked = new TaskCompletionSource(); var cont = new TaskCompletionSource(); - Task t1 = Task.Run(async () => - { - using (await mutex.LockAsync()) + Task t1 = Task.Run( + async () => { - locked.SetResult(true); - await cont.Task; - } - }); + using (await mutex.LockAsync()) + { + locked.SetResult(true); + await cont.Task; + } + }); await locked.Task; Task> t2Start = Task.Factory.StartNew(async () => await mutex.LockAsync()); @@ -48,28 +51,31 @@ public async Task TestLockedPreventsLockUntilUnlocked() await t1; } - [Fact, Unit] + [Fact] + [Unit] public async Task TestDoubleDisposeOnlyPermitsOneTask() { var mutex = new AsyncLock(); var t1HasLock = new TaskCompletionSource(); var t1Continue = new TaskCompletionSource(); - await Task.Run(async () => - { - AsyncLock.Releaser key = await mutex.LockAsync(); - key.Dispose(); - key.Dispose(); - }); + await Task.Run( + async () => + { + AsyncLock.Releaser key = await mutex.LockAsync(); + key.Dispose(); + key.Dispose(); + }); - Task t1 = Task.Run(async () => - { - using (await mutex.LockAsync()) + Task t1 = Task.Run( + async () => { - t1HasLock.SetResult(true); - await t1Continue.Task; - } - }); + using (await mutex.LockAsync()) + { + t1HasLock.SetResult(true); + await t1Continue.Task; + } + }); await t1HasLock.Task; Task> task2Start = Task.Factory.StartNew(async () => await mutex.LockAsync()); @@ -81,7 +87,8 @@ await Task.Run(async () => await t1; } - [Fact, Unit] + [Fact] + [Unit] public async Task TestCancellation() { var mutex = new AsyncLock(); @@ -95,7 +102,8 @@ public async Task TestCancellation() mutex.Dispose(); } - [Fact, Unit] + [Fact] + [Unit] public async Task TestThrowsIfTokenCancelled() { var mutex = new AsyncLock(); @@ -112,4 +120,4 @@ async Task F1() await Assert.ThrowsAsync(F1); } } -} \ No newline at end of file +} diff --git a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AtomicBooleanTest.cs b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AtomicBooleanTest.cs index 0134b7a6fca..da9fbf6b285 100644 --- a/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AtomicBooleanTest.cs +++ b/edge-hub/test/Microsoft.Azure.Devices.Routing.Core.Test/util/concurrency/AtomicBooleanTest.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Routing.Core.Test.Util.Concurrency { - using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Microsoft.Azure.Devices.Routing.Core.Util.Concurrency; using Xunit; public class AtomicBooleanTest { - [Fact, Unit] + [Fact] + [Unit] public void TestDefault() { var b = new AtomicBoolean(); @@ -19,7 +20,8 @@ public void TestDefault() Assert.Equal(true, b2); } - [Fact, Unit] + [Fact] + [Unit] public void TestSet() { var b = new AtomicBoolean(true); @@ -30,7 +32,8 @@ public void TestSet() Assert.Equal(true, b.Get()); } - [Fact, Unit] + [Fact] + [Unit] public void TestGetAndSet() { var b1 = new AtomicBoolean(true); @@ -39,7 +42,8 @@ public void TestGetAndSet() Assert.Equal(false, b1.Get()); } - [Fact, Unit] + [Fact] + [Unit] public void TestCompareAndSet() { var b1 = new AtomicBoolean(true); @@ -56,4 +60,4 @@ public void TestCompareAndSet() Assert.Equal(true, b1.Get()); } } -} \ No newline at end of file +} diff --git a/edge-modules/DirectMethodReceiver/DirectMethodReceiver.csproj b/edge-modules/DirectMethodReceiver/DirectMethodReceiver.csproj index 73f7b2bf34f..463545d0fa6 100644 --- a/edge-modules/DirectMethodReceiver/DirectMethodReceiver.csproj +++ b/edge-modules/DirectMethodReceiver/DirectMethodReceiver.csproj @@ -46,4 +46,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/edge-modules/DirectMethodReceiver/src/Program.cs b/edge-modules/DirectMethodReceiver/src/Program.cs index 51e91d16925..11a8eccb66a 100644 --- a/edge-modules/DirectMethodReceiver/src/Program.cs +++ b/edge-modules/DirectMethodReceiver/src/Program.cs @@ -16,6 +16,16 @@ class Program { public static int Main() => MainAsync().Result; + /// + /// Handles cleanup operations when app is cancelled or unloads + /// + public static Task WhenCancelled(CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs); + return tcs.Task; + } + static async Task MainAsync() { Console.WriteLine($"[{DateTime.UtcNow.ToString("MM/dd/yyyy hh:mm:ss.fff tt", CultureInfo.InvariantCulture)}] Main()"); @@ -39,16 +49,6 @@ static async Task MainAsync() return 0; } - /// - /// Handles cleanup operations when app is cancelled or unloads - /// - public static Task WhenCancelled(CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs); - return tcs.Task; - } - static async Task InitModuleClient(TransportType transportType) { ITransportSettings[] GetTransportSettings() @@ -63,6 +63,7 @@ ITransportSettings[] GetTransportSettings() return new ITransportSettings[] { new AmqpTransportSettings(transportType) }; } } + ITransportSettings[] settings = GetTransportSettings(); ModuleClient moduleClient = await ModuleClient.CreateFromEnvironmentAsync(settings).ConfigureAwait(false); diff --git a/edge-modules/DirectMethodSender/DirectMethodSender.csproj b/edge-modules/DirectMethodSender/DirectMethodSender.csproj index 73f7b2bf34f..463545d0fa6 100644 --- a/edge-modules/DirectMethodSender/DirectMethodSender.csproj +++ b/edge-modules/DirectMethodSender/DirectMethodSender.csproj @@ -46,4 +46,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/edge-modules/DirectMethodSender/config/appsettings.json b/edge-modules/DirectMethodSender/config/appsettings.json index 4cf598ff2a5..41cd24a6da3 100644 --- a/edge-modules/DirectMethodSender/config/appsettings.json +++ b/edge-modules/DirectMethodSender/config/appsettings.json @@ -1,5 +1,5 @@ { - "EdgeHubConnectionString": "", - "TargetModuleId" : "DirectMethodReceiver", - "DMDelay": "00:00:05" -} + "EdgeHubConnectionString": "", + "TargetModuleId": "DirectMethodReceiver", + "DMDelay": "00:00:05" +} \ No newline at end of file diff --git a/edge-modules/DirectMethodSender/src/Program.cs b/edge-modules/DirectMethodSender/src/Program.cs index 7c8da3e3082..7194025bffa 100644 --- a/edge-modules/DirectMethodSender/src/Program.cs +++ b/edge-modules/DirectMethodSender/src/Program.cs @@ -63,6 +63,7 @@ ITransportSettings[] GetTransportSettings() return new ITransportSettings[] { new AmqpTransportSettings(transportType) }; } } + ITransportSettings[] settings = GetTransportSettings(); ModuleClient moduleClient = await ModuleClient.CreateFromEnvironmentAsync(settings).ConfigureAwait(false); @@ -86,7 +87,7 @@ static async Task CallDirectMethod( ModuleClient moduleClient, TimeSpan dmDelay, string targetDeviceId, - string targetModuleId, + string targetModuleId, CancellationTokenSource cts) { while (!cts.Token.IsCancellationRequested) @@ -98,7 +99,7 @@ static async Task CallDirectMethod( try { - //Ignore Exception. Keep trying. + // Ignore Exception. Keep trying. MethodResponse response = await moduleClient.InvokeMethodAsync(targetDeviceId, targetModuleId, request); if (response.Status == (int)HttpStatusCode.OK) @@ -110,7 +111,6 @@ static async Task CallDirectMethod( { Console.WriteLine(e); } - await Task.Delay(dmDelay, cts.Token).ConfigureAwait(false); } diff --git a/edge-modules/MessagesAnalyzer/MessageDetails.cs b/edge-modules/MessagesAnalyzer/MessageDetails.cs index e10483b1f24..9a08aa70508 100644 --- a/edge-modules/MessagesAnalyzer/MessageDetails.cs +++ b/edge-modules/MessagesAnalyzer/MessageDetails.cs @@ -5,14 +5,14 @@ namespace MessagesAnalyzer class MessageDetails { - public long SequenceNumber { get; } - - public DateTime EnquedDateTime { get; } - public MessageDetails(long seqNumber, DateTime enquedDateTime) { this.SequenceNumber = seqNumber; this.EnquedDateTime = enquedDateTime; } + + public long SequenceNumber { get; } + + public DateTime EnquedDateTime { get; } } } diff --git a/edge-modules/MessagesAnalyzer/MessagesAnalyzer.csproj b/edge-modules/MessagesAnalyzer/MessagesAnalyzer.csproj index 693336c7ce2..fdb5a8acc5d 100644 --- a/edge-modules/MessagesAnalyzer/MessagesAnalyzer.csproj +++ b/edge-modules/MessagesAnalyzer/MessagesAnalyzer.csproj @@ -37,4 +37,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/edge-modules/MessagesAnalyzer/MessagesCache.cs b/edge-modules/MessagesAnalyzer/MessagesCache.cs index bac5f7948cc..f2b8e6e4779 100644 --- a/edge-modules/MessagesAnalyzer/MessagesCache.cs +++ b/edge-modules/MessagesAnalyzer/MessagesCache.cs @@ -9,11 +9,14 @@ class MessagesCache { // maps batchId with moduleId, there can be multiple batches for a module readonly ConcurrentDictionary batches = new ConcurrentDictionary(); + // maps batchId with messages readonly ConcurrentDictionary> messages = new ConcurrentDictionary>(); readonly IComparer comparer = new EventDataComparer(); - MessagesCache() { } + MessagesCache() + { + } public static MessagesCache Instance { get; } = new MessagesCache(); diff --git a/edge-modules/MessagesAnalyzer/MissedMessagesDetails.cs b/edge-modules/MessagesAnalyzer/MissedMessagesDetails.cs index 12bd857aaa8..b168d16072c 100644 --- a/edge-modules/MessagesAnalyzer/MissedMessagesDetails.cs +++ b/edge-modules/MessagesAnalyzer/MissedMessagesDetails.cs @@ -8,17 +8,17 @@ namespace MessagesAnalyzer [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] class MissedMessagesDetails { - public long MissedMessagesCount { get; } - - public DateTime StartDateTime { get; } - - public DateTime EndDateTime { get; } - public MissedMessagesDetails(long missedMessagesCount, DateTime startDateTime, DateTime endDateTime) { this.MissedMessagesCount = missedMessagesCount; this.StartDateTime = startDateTime; this.EndDateTime = endDateTime; } + + public long MissedMessagesCount { get; } + + public DateTime StartDateTime { get; } + + public DateTime EndDateTime { get; } } } diff --git a/edge-modules/MessagesAnalyzer/ModuleReport.cs b/edge-modules/MessagesAnalyzer/ModuleReport.cs index b66fea7c3c7..f7b58726338 100644 --- a/edge-modules/MessagesAnalyzer/ModuleReport.cs +++ b/edge-modules/MessagesAnalyzer/ModuleReport.cs @@ -9,11 +9,13 @@ namespace MessagesAnalyzer [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] class ModuleReport { - public ModuleReport(string moduleId, StatusCode statusCode, long receivedMessagesCount, string statusMessage) : this(moduleId, statusCode, receivedMessagesCount, statusMessage, DateTime.MinValue, new List()) + public ModuleReport(string moduleId, StatusCode statusCode, long receivedMessagesCount, string statusMessage) + : this(moduleId, statusCode, receivedMessagesCount, statusMessage, DateTime.MinValue, new List()) { } - public ModuleReport(string moduleId, StatusCode statusCode, long receivedMessagesCount, string statusMessage, DateTime lastMessageReceivedAt) : this(moduleId, statusCode, receivedMessagesCount, statusMessage, lastMessageReceivedAt, new List()) + public ModuleReport(string moduleId, StatusCode statusCode, long receivedMessagesCount, string statusMessage, DateTime lastMessageReceivedAt) + : this(moduleId, statusCode, receivedMessagesCount, statusMessage, lastMessageReceivedAt, new List()) { } @@ -37,10 +39,7 @@ public ModuleReport(string moduleId, StatusCode statusCode, long receivedMessage public DateTime LastMessageReceivedAt { get; } - public IList MissedMessages - { - get; - } + public IList MissedMessages { get; } public override string ToString() { diff --git a/edge-modules/MessagesAnalyzer/PartitionReceiverHandler.cs b/edge-modules/MessagesAnalyzer/PartitionReceiverHandler.cs index 75d64bca6ea..c48f7964fb9 100644 --- a/edge-modules/MessagesAnalyzer/PartitionReceiverHandler.cs +++ b/edge-modules/MessagesAnalyzer/PartitionReceiverHandler.cs @@ -10,12 +10,12 @@ namespace MessagesAnalyzer class PartitionReceiveHandler : IPartitionReceiveHandler { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const string DeviceIdPropertyName = "iothub-connection-device-id"; const string ModuleIdPropertyName = "iothub-connection-module-id"; const string SequenceNumberPropertyName = "sequenceNumber"; const string EnqueuedTimePropertyName = "iothub-enqueuedtime"; const string BatchIdPropertyName = "batchId"; + static readonly ILogger Log = Logger.Factory.CreateLogger(); readonly string deviceId; readonly IList excludedModulesIds; @@ -25,6 +25,8 @@ public PartitionReceiveHandler(string deviceId, IList excludedModulesIds this.excludedModulesIds = excludedModulesIds; } + public int MaxBatchSize { get; set; } + public Task ProcessEventsAsync(IEnumerable events) { if (events != null) @@ -41,7 +43,7 @@ public Task ProcessEventsAsync(IEnumerable events) eventData.Properties.TryGetValue(BatchIdPropertyName, out object batchId); if (sequence != null && batchId != null) - { + { DateTime enqueuedtime = DateTime.MinValue.ToUniversalTime(); if (eventData.SystemProperties.TryGetValue(EnqueuedTimePropertyName, out object enqueued)) { @@ -72,7 +74,5 @@ public Task ProcessErrorAsync(Exception error) Log.LogError(error.StackTrace); return Task.CompletedTask; } - - public int MaxBatchSize { get; set; } } } diff --git a/edge-modules/MessagesAnalyzer/Reporter.cs b/edge-modules/MessagesAnalyzer/Reporter.cs index e881d3c197f..8bafd1e2d7c 100644 --- a/edge-modules/MessagesAnalyzer/Reporter.cs +++ b/edge-modules/MessagesAnalyzer/Reporter.cs @@ -72,11 +72,14 @@ static ModuleReport GetReceivedMessagesReport(string moduleId, double toleranceI } } - //check if last message is older + // check if last message is older if (DateTime.Compare(lastMessageDateTime.AddMilliseconds(toleranceInMilliseconds), endDateTime) < 0) + { return new ModuleReport(moduleId, StatusCode.OldMessages, totalMessagesCounter, $"No messages received for the past {toleranceInMilliseconds} milliseconds", lastMessageDateTime, missingIntervals); + } - return missingCounter > 0 ? new ModuleReport(moduleId, StatusCode.SkippedMessages, totalMessagesCounter, $"Missing messages: {missingCounter}", lastMessageDateTime, missingIntervals) + return missingCounter > 0 + ? new ModuleReport(moduleId, StatusCode.SkippedMessages, totalMessagesCounter, $"Missing messages: {missingCounter}", lastMessageDateTime, missingIntervals) : new ModuleReport(moduleId, StatusCode.AllMessages, totalMessagesCounter, $"All messages received", lastMessageDateTime); } } diff --git a/edge-modules/MessagesAnalyzer/Settings.cs b/edge-modules/MessagesAnalyzer/Settings.cs index c469d7747b8..3051a1f0550 100644 --- a/edge-modules/MessagesAnalyzer/Settings.cs +++ b/edge-modules/MessagesAnalyzer/Settings.cs @@ -26,16 +26,14 @@ class Settings .AddEnvironmentVariables() .Build(); - IList excludedModules = configuration.GetSection(ExcludeModulesIdsPropertyName).Get>() ?? new List(); - - return new Settings(configuration.GetValue(EventHubConnectionStringPropertyValue), + return new Settings( + configuration.GetValue(EventHubConnectionStringPropertyValue), configuration.GetValue(DeviceIdPropertyName, DefaultDeviceId), excludedModules, configuration.GetValue(WebhostPortPropertyName, DefaultWebhostPort), configuration.GetValue(ToleranceInMillisecondsPropertyName, DefaultToleranceInMilliseconds)); - }); Settings(string eventHubCs, string deviceId, IList excludedModuleIds, string webhostPort, double tolerance) @@ -55,7 +53,7 @@ class Settings public string DeviceId { get; } - public string WebhostPort { get;} + public string WebhostPort { get; } public double ToleranceInMilliseconds { get; } } diff --git a/edge-modules/MessagesAnalyzer/Startup.cs b/edge-modules/MessagesAnalyzer/Startup.cs index cd47ae6a16a..c03d0e3cab9 100644 --- a/edge-modules/MessagesAnalyzer/Startup.cs +++ b/edge-modules/MessagesAnalyzer/Startup.cs @@ -10,7 +10,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -28,7 +28,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseDeveloperExceptionPage(); } - + app.UseMvc(); } } diff --git a/edge-modules/SimulatedTemperatureSensor/SimulatedTemperatureSensor.csproj b/edge-modules/SimulatedTemperatureSensor/SimulatedTemperatureSensor.csproj index 73f7b2bf34f..463545d0fa6 100644 --- a/edge-modules/SimulatedTemperatureSensor/SimulatedTemperatureSensor.csproj +++ b/edge-modules/SimulatedTemperatureSensor/SimulatedTemperatureSensor.csproj @@ -46,4 +46,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/edge-modules/SimulatedTemperatureSensor/src/MessageBody.cs b/edge-modules/SimulatedTemperatureSensor/src/MessageBody.cs index 0142393951a..06986265da1 100644 --- a/edge-modules/SimulatedTemperatureSensor/src/MessageBody.cs +++ b/edge-modules/SimulatedTemperatureSensor/src/MessageBody.cs @@ -15,7 +15,7 @@ namespace SimulatedTemperatureSensor /// “pressure”: /// }, /// “ambient”:{ - /// “temperature”: , + /// “temperature”: , /// “humidity”: /// } /// “timeCreated”:”UTC iso format” diff --git a/edge-modules/SimulatedTemperatureSensor/src/Program.cs b/edge-modules/SimulatedTemperatureSensor/src/Program.cs index 5c7c7fa3791..922ac4b0aac 100644 --- a/edge-modules/SimulatedTemperatureSensor/src/Program.cs +++ b/edge-modules/SimulatedTemperatureSensor/src/Program.cs @@ -22,12 +22,18 @@ class Program const int RetryCount = 5; const string MessageCountConfigKey = "MessageCount"; static readonly ITransientErrorDetectionStrategy TimeoutErrorDetectionStrategy = new DelegateErrorDetectionStrategy(ex => ex.HasTimeoutException()); + static readonly RetryStrategy TransientRetryStrategy = new ExponentialBackoff(RetryCount, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); + static readonly Random Rnd = new Random(); static readonly AtomicBoolean Reset = new AtomicBoolean(false); - public enum ControlCommandEnum { Reset = 0, Noop = 1 }; + public enum ControlCommandEnum + { + Reset = 0, + Noop = 1 + } public static int Main() => MainAsync().Result; @@ -55,7 +61,8 @@ static async Task MainAsync() }; string messagesToSendString = sendForever ? "unlimited" : messageCount.ToString(); - Console.WriteLine($"Initializing simulated temperature sensor to send {messagesToSendString} messages, at an interval of {messageDelay.TotalSeconds} seconds.\n" + Console.WriteLine( + $"Initializing simulated temperature sensor to send {messagesToSendString} messages, at an interval of {messageDelay.TotalSeconds} seconds.\n" + $"To change this, set the environment variable {MessageCountConfigKey} to the number of messages that should be sent (set it to -1 to send unlimited messages)."); TransportType transportType = configuration.GetValue("ClientTransportType", TransportType.Amqp_Tcp_Only); @@ -98,6 +105,7 @@ ITransportSettings[] GetTransportSettings() return new ITransportSettings[] { new AmqpTransportSettings(transportType) }; } } + ITransportSettings[] settings = GetTransportSettings(); ModuleClient moduleClient = await ModuleClient.CreateFromEnvironmentAsync(settings); @@ -170,10 +178,10 @@ static Task ResetMethod(MethodRequest methodRequest, object user /// Module behavior: /// Sends data periodically (with default frequency of 5 seconds). /// Data trend: - /// - Machine Temperature regularly rises from 21C to 100C in regularly with jitter - /// - Machine Pressure correlates with Temperature 1 to 10psi - /// - Ambient temperature stable around 21C - /// - Humidity is stable with tiny jitter around 25% + /// - Machine Temperature regularly rises from 21C to 100C in regularly with jitter + /// - Machine Pressure correlates with Temperature 1 to 10psi + /// - Ambient temperature stable around 21C + /// - Humidity is stable with tiny jitter around 25% /// Method for resetting the data stream /// static async Task SendEvents( @@ -195,6 +203,7 @@ static async Task SendEvents( currentTemp = sim.MachineTempMin; Reset.Set(false); } + if (currentTemp > sim.MachineTempMax) { currentTemp += Rnd.NextDouble() - 0.5; // add value between [-0.5..0.5] diff --git a/edge-modules/TemperatureFilter/MessageBody.cs b/edge-modules/TemperatureFilter/MessageBody.cs index 9dd379ea253..711bf90edab 100644 --- a/edge-modules/TemperatureFilter/MessageBody.cs +++ b/edge-modules/TemperatureFilter/MessageBody.cs @@ -15,7 +15,7 @@ namespace TemperatureFilter /// “pressure”: /// }, /// “ambient”:{ - /// “temperature”: , + /// “temperature”: , /// “humidity”: /// } /// “timeCreated”:”UTC iso format” diff --git a/edge-modules/TemperatureFilter/Program.cs b/edge-modules/TemperatureFilter/Program.cs index 70f2badf160..9a4a12a869f 100644 --- a/edge-modules/TemperatureFilter/Program.cs +++ b/edge-modules/TemperatureFilter/Program.cs @@ -21,16 +21,24 @@ namespace TemperatureFilter class Program { const int RetryCount = 5; - static readonly ITransientErrorDetectionStrategy TimeoutErrorDetectionStrategy = new DelegateErrorDetectionStrategy(ex => ex.HasTimeoutException()); - static readonly RetryStrategy TransientRetryStrategy = - new ExponentialBackoff(RetryCount, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); - const string TemperatureThresholdKey = "TemperatureThreshold"; const int DefaultTemperatureThreshold = 25; + static readonly ITransientErrorDetectionStrategy TimeoutErrorDetectionStrategy = new DelegateErrorDetectionStrategy(ex => ex.HasTimeoutException()); + static readonly RetryStrategy TransientRetryStrategy = new ExponentialBackoff(RetryCount, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); static int counter; public static int Main() => MainAsync().Result; + /// + /// Handles cleanup operations when app is cancelled or unloads + /// + public static Task WhenCancelled(CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs); + return tcs.Task; + } + static async Task MainAsync() { Console.WriteLine($"[{DateTime.UtcNow.ToString("MM/dd/yyyy hh:mm:ss.fff tt", CultureInfo.InvariantCulture)}] Main()"); @@ -57,8 +65,6 @@ static async Task MainAsync() Tuple userContext = moduleclientAndConfig; - - await moduleclientAndConfig.Item1.SetInputMessageHandlerAsync("input1", PrintAndFilterMessages, userContext).ConfigureAwait(false); // Wait until the app unloads or is cancelled @@ -69,16 +75,6 @@ static async Task MainAsync() return 0; } - /// - /// Handles cleanup operations when app is cancelled or unloads - /// - public static Task WhenCancelled(CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs); - return tcs.Task; - } - static async Task> InitModuleClient(TransportType transportType) { ITransportSettings[] GetTransportSettings() @@ -93,6 +89,7 @@ ITransportSettings[] GetTransportSettings() return new ITransportSettings[] { new AmqpTransportSettings(transportType) }; } } + ITransportSettings[] settings = GetTransportSettings(); ModuleClient moduleClient = await ModuleClient.CreateFromEnvironmentAsync(settings).ConfigureAwait(false); @@ -119,9 +116,11 @@ static async Task PrintAndFilterMessages(Message message, objec var userContextValues = userContext as Tuple; if (userContextValues == null) { - throw new InvalidOperationException("UserContext doesn't contain " + + throw new InvalidOperationException( + "UserContext doesn't contain " + "expected values"); } + ModuleClient moduleClient = userContextValues.Item1; ModuleConfig moduleModuleConfig = userContextValues.Item2; @@ -135,7 +134,8 @@ static async Task PrintAndFilterMessages(Message message, objec if (messageBody != null && messageBody.Machine.Temperature > moduleModuleConfig.TemperatureThreshold) { - Console.WriteLine($"Temperature {messageBody.Machine.Temperature} " + + Console.WriteLine( + $"Temperature {messageBody.Machine.Temperature} " + $"exceeds threshold {moduleModuleConfig.TemperatureThreshold}"); var filteredMessage = new Message(messageBytes); foreach (KeyValuePair prop in message.Properties) @@ -162,9 +162,9 @@ static async Task GetConfiguration(ModuleClient moduleClient) int tempThreshold = (int)twin.Properties.Desired[TemperatureThresholdKey]; return new ModuleConfig(tempThreshold); } - // Else try to get it from the environment variables. else { + // Else try to get it from the environment variables. string tempThresholdEnvVar = Environment.GetEnvironmentVariable(TemperatureThresholdKey); if (!string.IsNullOrWhiteSpace(tempThresholdEnvVar) && int.TryParse(tempThresholdEnvVar, out int tempThreshold)) { @@ -188,6 +188,5 @@ public ModuleConfig(int temperatureThreshold) public int TemperatureThreshold { get; } } - } } diff --git a/edge-modules/TemperatureFilter/TemperatureFilter.csproj b/edge-modules/TemperatureFilter/TemperatureFilter.csproj index 7e1310105ce..04aa544e6fb 100644 --- a/edge-modules/TemperatureFilter/TemperatureFilter.csproj +++ b/edge-modules/TemperatureFilter/TemperatureFilter.csproj @@ -36,4 +36,12 @@ + + + + + + ..\..\stylecop.ruleset + + diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/AsyncLock.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/AsyncLock.cs index 255232306d1..b25e20e44c9 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/AsyncLock.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/AsyncLock.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { - // // Code ported from http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx - // using System; using System.Threading; using System.Threading.Tasks; @@ -29,10 +27,14 @@ public AsyncLock(int maximumConcurrency) public Task LockAsync(CancellationToken token) { Task wait = this.semaphore.WaitAsync(token); - return wait.Status == TaskStatus.RanToCompletion ? this.releaser : - wait.ContinueWith((_, state) => new Releaser((AsyncLock)state), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + return wait.Status == TaskStatus.RanToCompletion + ? this.releaser + : wait.ContinueWith( + (_, state) => new Releaser((AsyncLock)state), + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, + TaskScheduler.Default); } /// diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/EdgeHubAsyncCollector.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/EdgeHubAsyncCollector.cs index c248ad97ed2..5ca9cdbc7c9 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/EdgeHubAsyncCollector.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/EdgeHubAsyncCollector.cs @@ -8,30 +8,30 @@ namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Client; - using Microsoft.Azure.WebJobs; /// - /// Core object to send events to EdgeHub. - /// Any user parameter that sends EdgeHub events will eventually get bound to this object. - /// This will queue events and send in batches, also keeping under the 256kb edge hub limit per batch. + /// Core object to send events to EdgeHub. + /// Any user parameter that sends EdgeHub events will eventually get bound to this object. + /// This will queue events and send in batches, also keeping under the 256kb edge hub limit per batch. /// public class EdgeHubAsyncCollector : IAsyncCollector { - readonly EdgeHubAttribute attribute; - readonly int batchSize; - - const int DefaultBatchSize = 10; // Max batch size limit from IoTHub const int MaxBatchSize = 500; // Suggested to use 240k instead of 256k to leave padding room for headers. const int MaxByteSize = 240 * 1024; + const int DefaultBatchSize = 10; + readonly EdgeHubAttribute attribute; + readonly int batchSize; readonly List list = new List(); - // total size of bytes in list that we'll be sending in this batch. + + // total size of bytes in list that we'll be sending in this batch. int currentByteSize; /// - /// Create a sender around the given client. + /// Initializes a new instance of the class. + /// Create a sender around the given client. /// /// Attributes used by EdgeHub when receiving a message from function. public EdgeHubAsyncCollector(EdgeHubAttribute attribute) @@ -45,7 +45,7 @@ public EdgeHubAsyncCollector(EdgeHubAttribute attribute) /// /// The event to add /// a cancellation token. - /// + /// Task public async Task AddAsync(Message message, CancellationToken cancellationToken = default(CancellationToken)) { byte[] payload = message.GetBytes(); @@ -83,6 +83,7 @@ public EdgeHubAsyncCollector(EdgeHubAttribute attribute) /// synchronously flush events that have been queued up via AddAsync. /// /// a cancellation token + /// Task public async Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) { IList batch = this.TakeSnapshot(); @@ -93,6 +94,7 @@ public EdgeHubAsyncCollector(EdgeHubAttribute attribute) /// Send the batch of events. /// /// the set of events to send + /// Task protected virtual async Task SendBatchAsync(IList batch) { if (batch == null || batch.Count == 0) @@ -121,6 +123,7 @@ IList TakeSnapshot() this.list.Clear(); this.currentByteSize = 0; } + return batch; } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Microsoft.Azure.WebJobs.Extensions.EdgeHub.csproj b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Microsoft.Azure.WebJobs.Extensions.EdgeHub.csproj index 3e398d1406a..b7a82cce7f3 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Microsoft.Azure.WebJobs.Extensions.EdgeHub.csproj +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Microsoft.Azure.WebJobs.Extensions.EdgeHub.csproj @@ -37,4 +37,12 @@ + + + + + + ..\..\..\..\..\stylecop.ruleset + + diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Utils.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Utils.cs index 0eb5a6065e2..1c3b680ff40 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Utils.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/Utils.cs @@ -23,6 +23,6 @@ public static Message GetMessageCopy(byte[] payload, Message message) public static bool HasTimeoutException(this Exception ex) => ex != null && (ex is TimeoutException || HasTimeoutException(ex.InnerException) || - (ex is AggregateException argEx && (argEx.InnerExceptions?.Select(e => HasTimeoutException(e)).Any(e => e) ?? false))); + (ex is AggregateException argEx && (argEx.InnerExceptions?.Select(e => HasTimeoutException(e)).Any(e => e) ?? false))); } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubCollectorBuilder.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubCollectorBuilder.cs index 01921608cdd..2d00c40bb61 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubCollectorBuilder.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubCollectorBuilder.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { using Microsoft.Azure.Devices.Client; - using Microsoft.Azure.WebJobs; class EdgeHubCollectorBuilder : IConverter> { diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubMessageProcessor.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubMessageProcessor.cs index 01b1ae8290e..5fe58160df6 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubMessageProcessor.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubMessageProcessor.cs @@ -9,10 +9,10 @@ namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub /// class EdgeHubMessageProcessor { - public delegate Task MessageHandler(Message message, object userContext); - MessageHandler handler; + public delegate Task MessageHandler(Message message, object userContext); + public Task TriggerMessage(Message message, object userContext) { return this.handler?.Invoke(message, userContext) ?? Task.CompletedTask; diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubTriggerBinding.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubTriggerBinding.cs index dbff71dd672..5488200d91f 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubTriggerBinding.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/bindings/EdgeHubTriggerBinding.cs @@ -7,12 +7,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub using System.Reflection; using System.Threading; using System.Threading.Tasks; - using Devices.Client; - using Host.Bindings; - using Host.Executors; - using Host.Listeners; - using Host.Protocols; - using Host.Triggers; + using Microsoft.Azure.Devices.Client; + using Microsoft.Azure.WebJobs.Host.Bindings; + using Microsoft.Azure.WebJobs.Host.Executors; + using Microsoft.Azure.WebJobs.Host.Listeners; + using Microsoft.Azure.WebJobs.Host.Protocols; + using Microsoft.Azure.WebJobs.Host.Triggers; /// /// Implements a trigger binding for EdgeHub which triggers a function diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubExtensionConfigProvider.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubExtensionConfigProvider.cs index b61eb8c79df..be708e495fd 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubExtensionConfigProvider.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubExtensionConfigProvider.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Text; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Host.Bindings; -using Newtonsoft.Json.Linq; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub.Config { using System; + using System.Text; + using System.Threading.Tasks; using Microsoft.Azure.Devices.Client; using Microsoft.Azure.WebJobs.Description; + using Microsoft.Azure.WebJobs.Host.Bindings; using Microsoft.Azure.WebJobs.Host.Config; using Newtonsoft.Json; @@ -35,17 +33,17 @@ public void Initialize(ExtensionConfigContext context) rule2.BindToCollector(typeof(EdgeHubCollectorBuilder)); rule2.AddConverter(ConvertStringToMessage); rule2.AddConverter(ConvertBytesToMessage); - rule2.AddOpenConverter(ConvertPocoToMessage); + rule2.AddOpenConverter(this.ConvertPocoToMessage); } - private Task ConvertPocoToMessage(object src, Attribute attribute, ValueBindingContext context) => Task.FromResult(ConvertStringToMessage(JsonConvert.SerializeObject(src))); + static Message ConvertBytesToMessage(byte[] msgBytes) => new Message(msgBytes); - private static Message ConvertBytesToMessage(byte[] msgBytes) => new Message(msgBytes); + static Message ConvertStringToMessage(string msg) => ConvertBytesToMessage(Encoding.UTF8.GetBytes(msg)); - private static Message ConvertStringToMessage(string msg) => ConvertBytesToMessage(Encoding.UTF8.GetBytes(msg)); + static byte[] ConvertMessageToBytes(Message msg) => msg.GetBytes(); - private static byte[] ConvertMessageToBytes(Message msg) => msg.GetBytes(); + static string ConvertMessageToString(Message msg) => Encoding.UTF8.GetString(ConvertMessageToBytes(msg)); - private static string ConvertMessageToString(Message msg) => Encoding.UTF8.GetString(ConvertMessageToBytes(msg)); + Task ConvertPocoToMessage(object src, Attribute attribute, ValueBindingContext context) => Task.FromResult(ConvertStringToMessage(JsonConvert.SerializeObject(src))); } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubHostConfigExtensions.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubHostConfigExtensions.cs index cbf0f905872..65a2d144f9c 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubHostConfigExtensions.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/config/EdgeHubHostConfigExtensions.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub.Config { using System; - using Microsoft.Azure.WebJobs; /// /// Extension methods for EdgeHub integration diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution.cs index 1fee74e857d..cb8e7138734 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution.cs @@ -29,38 +29,55 @@ static Task StartAsGenericTask(Func taskAction) Task task = taskAction(); if (task == null) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} cannot be null", new object[] - { - "taskAction" - }), nameof(taskAction)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} cannot be null", + new object[] + { + "taskAction" + }), + nameof(taskAction)); } + if (task.Status == TaskStatus.RanToCompletion) { return GetCachedTask(); } + if (task.Status == TaskStatus.Created) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} must be scheduled", new object[] - { - "taskAction" - }), nameof(taskAction)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} must be scheduled", + new object[] + { + "taskAction" + }), + nameof(taskAction)); } + var tcs = new TaskCompletionSource(); - task.ContinueWith(delegate (Task t) - { - if (t.IsFaulted) - { - if (t.Exception != null) - tcs.TrySetException(t.Exception.InnerExceptions); - return; - } - if (t.IsCanceled) + task.ContinueWith( + t => { - tcs.TrySetCanceled(); - return; - } - tcs.TrySetResult(true); - }, TaskContinuationOptions.ExecuteSynchronously); + if (t.IsFaulted) + { + if (t.Exception != null) + tcs.TrySetException(t.Exception.InnerExceptions); + return; + } + + if (t.IsCanceled) + { + tcs.TrySetCanceled(); + return; + } + + tcs.TrySetResult(true); + }, + TaskContinuationOptions.ExecuteSynchronously); return tcs.Task; } @@ -72,6 +89,7 @@ static Task GetCachedTask() taskCompletionSource.TrySetResult(true); cachedBoolTask = taskCompletionSource.Task; } + return cachedBoolTask; } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution[T].cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution[T].cs index 779e0b75a0c..ff92abd68da 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution[T].cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/AsyncExecution[T].cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + /// /// Handles the execution and retries of the user-initiated task. /// @@ -51,6 +51,7 @@ Task ExecuteAsyncImpl(Task ignore) { return this.previousTask; } + var taskCompletionSource = new TaskCompletionSource(); taskCompletionSource.TrySetCanceled(); return taskCompletionSource.Task; @@ -68,28 +69,43 @@ Task ExecuteAsyncImpl(Task ignore) { throw; } + var taskCompletionSource2 = new TaskCompletionSource(); taskCompletionSource2.TrySetException(ex); task = taskCompletionSource2.Task; } + if (task == null) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} cannot be null", new object[] - { - "taskFunc" - }), nameof(this.taskFunc)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} cannot be null", + new object[] + { + "taskFunc" + }), + nameof(this.taskFunc)); } + if (task.Status == TaskStatus.RanToCompletion) { return task; } + if (task.Status == TaskStatus.Created) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} must be scheduled", new object[] - { - "taskFunc" - }), nameof(this.taskFunc)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} must be scheduled", + new object[] + { + "taskFunc" + }), + nameof(this.taskFunc)); } + return task.ContinueWith(new Func, Task>(this.ExecuteAsyncContinueWith), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap(); } } @@ -100,6 +116,7 @@ Task ExecuteAsyncContinueWith(Task runningTask) { return runningTask; } + TimeSpan zero; Exception innerException = runningTask.Exception.InnerException; #pragma warning disable CS0618 // Type or member is obsolete @@ -115,22 +132,27 @@ Task ExecuteAsyncContinueWith(Task runningTask) { taskCompletionSource.TrySetCanceled(); } + return taskCompletionSource.Task; } + if (!this.isTransient(innerException) || !this.shouldRetry(this.retryCount++, innerException, out zero)) { return runningTask; } + if (zero < TimeSpan.Zero) { zero = TimeSpan.Zero; } + this.onRetrying(this.retryCount, innerException, zero); this.previousTask = runningTask; if (zero > TimeSpan.Zero && (this.retryCount > 1 || !this.fastFirstRetry)) { return Task.Delay(zero, this.cancellationToken).ContinueWith(new Func>(this.ExecuteAsyncImpl), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap(); } + return this.ExecuteAsyncImpl(null); } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ExponentialBackoff.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ExponentialBackoff.cs index 29f79d1b2d7..98ab990f6e2 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ExponentialBackoff.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ExponentialBackoff.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// A retry strategy with back-off parameters for calculating the exponential delay between retries. /// Note: this fixes an overflow in the stock ExponentialBackoff in the Transient Fault Handling library diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/FixedInterval.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/FixedInterval.cs index ec3a9d8c023..8b026ebf0a0 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/FixedInterval.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/FixedInterval.cs @@ -1,48 +1,52 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// Represents a retry strategy with a specified number of retry attempts and a default, fixed time interval between retries. /// class FixedInterval : RetryStrategy { - private readonly int retryCount; + readonly int retryCount; - private readonly TimeSpan retryInterval; + readonly TimeSpan retryInterval; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public FixedInterval() : this(DefaultClientRetryCount) + public FixedInterval() + : this(DefaultClientRetryCount) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts. + /// Initializes a new instance of the class with the specified number of retry attempts. /// /// The number of retry attempts. - public FixedInterval(int retryCount) : this(retryCount, DefaultRetryInterval) + public FixedInterval(int retryCount) + : this(retryCount, DefaultRetryInterval) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts, time interval, and retry strategy. + /// Initializes a new instance of the class with the specified number of retry attempts, time interval, and retry strategy. /// /// The number of retry attempts. /// The time interval between retries. - public FixedInterval(int retryCount, TimeSpan retryInterval) : this(retryCount, retryInterval, DefaultFirstFastRetry) + public FixedInterval(int retryCount, TimeSpan retryInterval) + : this(retryCount, retryInterval, DefaultFirstFastRetry) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. + /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. /// /// The number of retry attempts. /// The time interval between retries. /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. - public FixedInterval(int retryCount, TimeSpan retryInterval, bool firstFastRetry) : base(firstFastRetry) + public FixedInterval(int retryCount, TimeSpan retryInterval, bool firstFastRetry) + : base(firstFastRetry) { Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); Guard.ArgumentNotNegativeValue(retryInterval.Ticks, "retryInterval"); @@ -58,19 +62,21 @@ public override ShouldRetry GetShouldRetry() { if (this.retryCount == 0) { - return delegate (int currentRetryCount, Exception lastException, out TimeSpan interval) + return (int currentRetryCount, Exception lastException, out TimeSpan interval) => { interval = TimeSpan.Zero; return false; }; } - return delegate (int currentRetryCount, Exception lastException, out TimeSpan interval) + + return (int currentRetryCount, Exception lastException, out TimeSpan interval) => { if (currentRetryCount < this.retryCount) { interval = this.retryInterval; return true; } + interval = TimeSpan.Zero; return false; }; diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Guard.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Guard.cs index 3fff0b927b7..395a6a75efc 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Guard.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Guard.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Globalization; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + using System.Globalization; + /// /// Implements the common guard methods. /// @@ -20,11 +20,16 @@ public static bool ArgumentNotNullOrEmptyString(string argumentValue, string arg ArgumentNotNull(argumentValue, argumentName); if (argumentValue.Length == 0) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "String {0} cannot be empty", new object[] - { - argumentName - })); + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + "String {0} cannot be empty", + new object[] + { + argumentName + })); } + return true; } @@ -40,6 +45,7 @@ public static bool ArgumentNotNull(object argumentValue, string argumentName) { throw new ArgumentNullException(argumentName); } + return true; } @@ -52,10 +58,16 @@ public static void ArgumentNotNegativeValue(int argumentValue, string argumentNa { if (argumentValue < 0) { - throw new ArgumentOutOfRangeException(argumentName, argumentValue, string.Format(CultureInfo.CurrentCulture, "Argument {0} cannot be negative", new object[] - { - argumentName - })); + throw new ArgumentOutOfRangeException( + argumentName, + argumentValue, + string.Format( + CultureInfo.CurrentCulture, + "Argument {0} cannot be negative", + new object[] + { + argumentName + })); } } @@ -68,10 +80,16 @@ public static void ArgumentNotNegativeValue(long argumentValue, string argumentN { if (argumentValue < 0L) { - throw new ArgumentOutOfRangeException(argumentName, argumentValue, string.Format(CultureInfo.CurrentCulture, "Argument {0} cannot be negative", new object[] - { - argumentName - })); + throw new ArgumentOutOfRangeException( + argumentName, + argumentValue, + string.Format( + CultureInfo.CurrentCulture, + "Argument {0} cannot be negative", + new object[] + { + argumentName + })); } } @@ -85,11 +103,17 @@ public static void ArgumentNotGreaterThan(double argumentValue, double ceilingVa { if (argumentValue > ceilingValue) { - throw new ArgumentOutOfRangeException(argumentName, argumentValue, string.Format(CultureInfo.CurrentCulture, "Argument {0} cannot be greater than baseline value {1}", new object[] - { + throw new ArgumentOutOfRangeException( argumentName, - ceilingValue - })); + argumentValue, + string.Format( + CultureInfo.CurrentCulture, + "Argument {0} cannot be greater than baseline value {1}", + new object[] + { + argumentName, + ceilingValue + })); } } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ITransientErrorDetectionStrategy.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ITransientErrorDetectionStrategy.cs index 2b8ae7c0c25..9e30bb3c9e9 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ITransientErrorDetectionStrategy.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ITransientErrorDetectionStrategy.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// Defines an interface that must be implemented by custom components responsible for detecting specific transient conditions. /// diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Incremental.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Incremental.cs index 20cc62131f1..cb23ebd8747 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Incremental.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/Incremental.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// A retry strategy with a specified number of retry attempts and an incremental time interval between retries. /// @@ -13,30 +13,33 @@ class Incremental : RetryStrategy readonly TimeSpan increment; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Incremental() : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) + public Incremental() + : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) { } /// - /// 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 number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive delay between retries. - public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment) : this(retryCount, initialInterval, increment, DefaultFirstFastRetry) + public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(retryCount, initialInterval, increment, DefaultFirstFastRetry) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. + /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. /// /// The number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive 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 Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) : base(firstFastRetry) + public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) + : base(firstFastRetry) { Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); Guard.ArgumentNotNegativeValue(initialInterval.Ticks, "initialInterval"); @@ -52,13 +55,14 @@ public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment, /// The ShouldRetry delegate. public override ShouldRetry GetShouldRetry() { - return delegate (int currentRetryCount, Exception lastException, out TimeSpan retryInterval) + return (int currentRetryCount, Exception lastException, out TimeSpan retryInterval) => { if (currentRetryCount < this.retryCount) { retryInterval = TimeSpan.FromMilliseconds(this.initialInterval.TotalMilliseconds + this.increment.TotalMilliseconds * currentRetryCount); return true; } + retryInterval = TimeSpan.Zero; return false; }; diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryLimitExceededException.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryLimitExceededException.cs index 34234142fb0..e24fb63d832 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryLimitExceededException.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryLimitExceededException.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// The special type of exception that provides managed exit from a retry loop. The user code can use this /// exception to notify the retry policy that no further retry attempts are required. @@ -13,7 +13,8 @@ sealed class RetryLimitExceededException : Exception /// /// Initializes a new instance of the class with a default error message. /// - public RetryLimitExceededException() : this("Retry limit exceeded") + public RetryLimitExceededException() + : this("Retry limit exceeded") { } @@ -21,7 +22,8 @@ public RetryLimitExceededException() : this("Retry limit exceeded") /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. - public RetryLimitExceededException(string message) : base(message) + public RetryLimitExceededException(string message) + : base(message) { } @@ -30,7 +32,8 @@ public RetryLimitExceededException(string message) : base(message) /// that is the cause of this exception. /// /// The exception that is the cause of the current exception. - public RetryLimitExceededException(Exception innerException) : base((innerException != null) ? innerException.Message : "Retry limit exceeded", innerException) + public RetryLimitExceededException(Exception innerException) + : base((innerException != null) ? innerException.Message : "Retry limit exceeded", innerException) { } @@ -39,7 +42,8 @@ public RetryLimitExceededException(Exception innerException) : base((innerExcept /// /// The message that describes the error. /// The exception that is the cause of the current exception. - public RetryLimitExceededException(string message, Exception innerException) : base(message, innerException) + public RetryLimitExceededException(string message, Exception innerException) + : base(message, innerException) { } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryPolicy.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryPolicy.cs index 330d159f321..b79e4d487e4 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryPolicy.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryPolicy.cs @@ -1,91 +1,15 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + using System.Threading; + using System.Threading.Tasks; + /// /// Provides the base implementation of the retry mechanism for unreliable actions and transient conditions. /// class RetryPolicy { - /// - /// Implements a strategy that ignores any transient errors. - /// - sealed class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy - { - /// - /// Always returns false. - /// - /// The exception. - /// Always false. - public bool IsTransient(Exception ex) - { - return false; - } - } - - /// - /// Implements a strategy that treats all exceptions as transient errors. - /// - sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy - { - /// - /// Always returns true. - /// - /// The exception. - /// Always true. - public bool IsTransient(Exception ex) - { - return true; - } - } - - /// - /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. - /// - public event EventHandler Retrying; - - /// - /// Returns a default policy that performs no retries, but invokes the action only once. - /// - public static RetryPolicy NoRetry { get; } = new RetryPolicy(new RetryPolicy.TransientErrorIgnoreStrategy(), RetryStrategy.NoRetry); - - /// - /// Returns a default policy that implements a fixed retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static RetryPolicy DefaultFixed { get; } = new RetryPolicy(new RetryPolicy.TransientErrorCatchAllStrategy(), RetryStrategy.DefaultFixed); - - /// - /// Returns a default policy that implements a progressive retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static RetryPolicy DefaultProgressive { get; } = new RetryPolicy(new RetryPolicy.TransientErrorCatchAllStrategy(), RetryStrategy.DefaultProgressive); - - /// - /// Returns a default policy that implements a random exponential retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static RetryPolicy DefaultExponential { get; } = new RetryPolicy(new RetryPolicy.TransientErrorCatchAllStrategy(), RetryStrategy.DefaultExponential); - - /// - /// Gets the retry strategy. - /// - public RetryStrategy RetryStrategy - { - get; - } - - /// - /// Gets the instance of the error detection strategy. - /// - public ITransientErrorDetectionStrategy ErrorDetectionStrategy - { - get; - } - /// /// Initializes a new instance of the class with the specified number of retry attempts and parameters defining the progressive delay between retries. /// @@ -100,6 +24,7 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr { throw new InvalidOperationException("The error detection strategy type must implement the ITransientErrorDetectionStrategy interface."); } + this.RetryStrategy = retryStrategy; } @@ -108,7 +33,8 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// /// The that is responsible for detecting transient conditions. /// The number of retry attempts. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) : this(errorDetectionStrategy, new FixedInterval(retryCount)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) + : this(errorDetectionStrategy, new FixedInterval(retryCount)) { } @@ -118,7 +44,8 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// The that is responsible for detecting transient conditions. /// The number of retry attempts. /// The interval between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) : this(errorDetectionStrategy, new FixedInterval(retryCount, retryInterval)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) + : this(errorDetectionStrategy, new FixedInterval(retryCount, retryInterval)) { } @@ -130,7 +57,8 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// The minimum backoff time. /// The maximum backoff time. /// The time value that will be used to calculate a random delta in the exponential delay between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) { } @@ -141,10 +69,49 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// The number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive delay between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, TimeSpan increment) : this(errorDetectionStrategy, new Incremental(retryCount, initialInterval, increment)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(errorDetectionStrategy, new Incremental(retryCount, initialInterval, increment)) { } + /// + /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. + /// + public event EventHandler Retrying; + + /// + /// Returns a default policy that performs no retries, but invokes the action only once. + /// + public static RetryPolicy NoRetry { get; } = new RetryPolicy(new TransientErrorIgnoreStrategy(), RetryStrategy.NoRetry); + + /// + /// Returns a default policy that implements a fixed retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static RetryPolicy DefaultFixed { get; } = new RetryPolicy(new TransientErrorCatchAllStrategy(), RetryStrategy.DefaultFixed); + + /// + /// Returns a default policy that implements a progressive retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static RetryPolicy DefaultProgressive { get; } = new RetryPolicy(new TransientErrorCatchAllStrategy(), RetryStrategy.DefaultProgressive); + + /// + /// Returns a default policy that implements a random exponential retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static RetryPolicy DefaultExponential { get; } = new RetryPolicy(new TransientErrorCatchAllStrategy(), RetryStrategy.DefaultExponential); + + /// + /// Gets the retry strategy. + /// + public RetryStrategy RetryStrategy { get; } + + /// + /// Gets the instance of the error detection strategy. + /// + public ITransientErrorDetectionStrategy ErrorDetectionStrategy { get; } + /// /// Repetitively executes the specified action while it satisfies the current retry policy. /// @@ -152,11 +119,12 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr public virtual void ExecuteAction(Action action) { Guard.ArgumentNotNull(action, "action"); - this.ExecuteAction(delegate - { - action(); - return null; - }); + this.ExecuteAction( + delegate + { + action(); + return null; + }); } /// @@ -188,6 +156,7 @@ public virtual TResult ExecuteAction(Func func) { throw ex2.InnerException; } + result = default(TResult); break; } @@ -199,16 +168,19 @@ public virtual TResult ExecuteAction(Func func) throw; } } + if (zero.TotalMilliseconds < 0.0) { zero = TimeSpan.Zero; } + this.OnRetrying(num, ex, zero); if (num > 1 || !this.RetryStrategy.FastFirstRetry) { Task.Delay(zero).Wait(); } } + return result; } @@ -242,6 +214,7 @@ public Task ExecuteAsync(Func taskAction, CancellationToken cancellationTo { throw new ArgumentNullException(nameof(taskAction)); } + return new AsyncExecution(taskAction, this.RetryStrategy.GetShouldRetry(), new Func(this.ErrorDetectionStrategy.IsTransient), new Action(this.OnRetrying), this.RetryStrategy.FastFirstRetry, cancellationToken).ExecuteAsync(); } @@ -275,6 +248,7 @@ public Task ExecuteAsync(Func> taskFunc, Cancell { throw new ArgumentNullException(nameof(taskFunc)); } + return new AsyncExecution(taskFunc, this.RetryStrategy.GetShouldRetry(), new Func(this.ErrorDetectionStrategy.IsTransient), new Action(this.OnRetrying), this.RetryStrategy.FastFirstRetry, cancellationToken).ExecuteAsync(); } @@ -288,5 +262,37 @@ protected virtual void OnRetrying(int retryCount, Exception lastError, TimeSpan { this.Retrying?.Invoke(this, new RetryingEventArgs(retryCount, lastError)); } + + /// + /// Implements a strategy that treats all exceptions as transient errors. + /// + sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always returns true. + /// + /// The exception. + /// Always true. + public bool IsTransient(Exception ex) + { + return true; + } + } + + /// + /// Implements a strategy that ignores any transient errors. + /// + sealed class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always returns false. + /// + /// The exception. + /// Always false. + public bool IsTransient(Exception ex) + { + return false; + } + } } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryStrategy.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryStrategy.cs index f7cb4f8be21..ba06c7aabaa 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryStrategy.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryStrategy.cs @@ -44,6 +44,15 @@ abstract class RetryStrategy /// public static readonly bool DefaultFirstFastRetry = true; + /// + /// Initializes a new instance of the class. + /// + /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. + protected RetryStrategy(bool firstFastRetry) + { + this.FastFirstRetry = firstFastRetry; + } + /// /// Returns a default policy that performs no retries, but invokes the action only once. /// @@ -71,20 +80,7 @@ abstract class RetryStrategy /// 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; - } - - /// - /// Initializes a new instance of the class. - /// - /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. - protected RetryStrategy(bool firstFastRetry) - { - this.FastFirstRetry = firstFastRetry; - } + public bool FastFirstRetry { get; set; } /// /// Returns the corresponding ShouldRetry delegate. diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryingEventArgs.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryingEventArgs.cs index 92041c527f0..7784f7d7bfc 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryingEventArgs.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/RetryingEventArgs.cs @@ -1,31 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// Contains information that is required for the event. /// class RetryingEventArgs : EventArgs { - /// - /// Gets the current retry count. - /// - public int CurrentRetryCount - { - get; - set; - } - - /// - /// Gets the exception that caused the retry conditions to occur. - /// - public Exception LastException - { - get; - set; - } - /// /// Initializes a new instance of the class. /// @@ -37,5 +19,15 @@ public RetryingEventArgs(int currentRetryCount, Exception lastException) this.CurrentRetryCount = currentRetryCount; this.LastException = lastException; } + + /// + /// Gets the current retry count. + /// + public int CurrentRetryCount { get; set; } + + /// + /// Gets the exception that caused the retry conditions to occur. + /// + public Exception LastException { get; set; } } } diff --git a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ShouldRetry.cs b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ShouldRetry.cs index 6d0279ef6ac..ef4bd4af34c 100644 --- a/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ShouldRetry.cs +++ b/edge-modules/functions/binding/src/Microsoft.Azure.WebJobs.Extensions.EdgeHub/transientFaultHandling/ShouldRetry.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.WebJobs.Extensions.EdgeHub { + using System; + /// /// Defines a callback delegate that will be invoked whenever a retry condition is encountered. /// diff --git a/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.cs b/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.cs index 278cb1ef47d..57f17388ac7 100644 --- a/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.cs +++ b/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.cs @@ -1,25 +1,25 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Azure.Devices.Client; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.EdgeHub; -using Newtonsoft.Json; - namespace Functions.Samples { + using System; + using System.Collections.Generic; + using System.Text; + using System.Threading.Tasks; + using Microsoft.Azure.Devices.Client; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.EdgeHub; + using Newtonsoft.Json; + public static class EdgeHubSamples { [FunctionName("EdgeHubTrigger-CSharp")] public static async Task FilterMessageAndSendMessage( - [EdgeHubTrigger("input1")] Message messageReceived, - [EdgeHub(OutputName = "output1")] IAsyncCollector output) + [EdgeHubTrigger("input1")] Message messageReceived, + [EdgeHub(OutputName = "output1")] IAsyncCollector output) { const int defaultTemperatureThreshold = 19; byte[] messageBytes = messageReceived.GetBytes(); - var messageString = System.Text.Encoding.UTF8.GetString(messageBytes); + var messageString = Encoding.UTF8.GetString(messageBytes); // Get message body, containing the Temperature data var messageBody = JsonConvert.DeserializeObject(messageString); @@ -37,13 +37,11 @@ public static async Task FilterMessageAndSendMessage( } } - public class MessageBody + public class Ambient { - public Machine Machine { get; set; } - - public Ambient Ambient { get; set; } + public double Temperature { get; set; } - public DateTime TimeCreated { get; set; } + public int Humidity { get; set; } } public class Machine @@ -53,11 +51,13 @@ public class Machine public double Pressure { get; set; } } - public class Ambient + public class MessageBody { - public double Temperature { get; set; } + public Machine Machine { get; set; } - public int Humidity { get; set; } + public Ambient Ambient { get; set; } + + public DateTime TimeCreated { get; set; } } } } diff --git a/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.csproj b/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.csproj index bef37557c41..733d6b7a9e2 100644 --- a/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.csproj +++ b/edge-modules/functions/samples/EdgeHubTrigger-Csharp/EdgeHubTriggerCSharp.csproj @@ -22,4 +22,12 @@ + + + + + + ..\..\..\..\stylecop.ruleset + + diff --git a/edge-modules/functions/samples/host.json b/edge-modules/functions/samples/host.json index 87082692934..9b6d367bdd1 100644 --- a/edge-modules/functions/samples/host.json +++ b/edge-modules/functions/samples/host.json @@ -1,9 +1,9 @@ { - "version": "2.0", - "logger": { - "fileLoggingMode": "always" - }, - "watchDirectories": [ "Shared" ], - "functionTimeout": "00:05:00", - "functions": [ "EdgeHubTrigger-Csharp" ] + "version": "2.0", + "logger": { + "fileLoggingMode": "always" + }, + "watchDirectories": ["Shared"], + "functionTimeout": "00:05:00", + "functions": ["EdgeHubTrigger-Csharp"] } \ No newline at end of file diff --git a/edge-modules/load-gen/BufferPool.cs b/edge-modules/load-gen/BufferPool.cs index d600f66db53..d1a4ed56e0c 100644 --- a/edge-modules/load-gen/BufferPool.cs +++ b/edge-modules/load-gen/BufferPool.cs @@ -1,30 +1,32 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Microsoft.Azure.Devices.Edge.Util.Concurrency; -using Serilog; - namespace LoadGen { + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using Microsoft.Azure.Devices.Edge.Util.Concurrency; + using Serilog; + public class BufferPool { - private ConcurrentDictionary> buffers = new ConcurrentDictionary>(); + readonly ConcurrentDictionary> buffers = new ConcurrentDictionary>(); public Buffer AllocBuffer(ulong size) { - List buffers = this.buffers.GetOrAdd(size, (bufSize) => - { - var list = new List + List buffers = this.buffers.GetOrAdd( + size, + (bufSize) => { - new Buffer(bufSize) - }; + var list = new List + { + new Buffer(bufSize) + }; - Log.Information($"Allocated new list & buffer [{list[0].Id}] of size {size}"); - return list; - }); + Log.Information($"Allocated new list & buffer [{list[0].Id}] of size {size}"); + return list; + }); lock (buffers) { @@ -48,9 +50,16 @@ public Buffer AllocBuffer(ulong size) public class Buffer : IDisposable { - static long BufferIdCounter = 0; + static long bufferIdCounter = 0; - private byte[] buffer; + readonly byte[] buffer; + + public Buffer(ulong size) + { + this.buffer = new byte[size]; + this.InUse = new AtomicBoolean(false); + this.Id = Interlocked.Increment(ref bufferIdCounter); + } public AtomicBoolean InUse { get; set; } @@ -61,13 +70,6 @@ public byte[] Data get { return this.buffer; } } - public Buffer(ulong size) - { - this.buffer = new byte[size]; - this.InUse = new AtomicBoolean(false); - this.Id = Interlocked.Increment(ref BufferIdCounter); - } - public void Dispose() { this.InUse.Set(false); diff --git a/edge-modules/load-gen/Program.cs b/edge-modules/load-gen/Program.cs index ca4a1de7484..1fef448081c 100644 --- a/edge-modules/load-gen/Program.cs +++ b/edge-modules/load-gen/Program.cs @@ -14,12 +14,15 @@ namespace LoadGen using Newtonsoft.Json; using Serilog; using ExponentialBackoff = Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling.ExponentialBackoff; + using ILogger = Microsoft.Extensions.Logging.ILogger; class Program { const int RetryCount = 5; + static readonly ITransientErrorDetectionStrategy TimeoutErrorDetectionStrategy = new DelegateErrorDetectionStrategy(ex => ex.HasTimeoutException()); + static readonly RetryStrategy TransientRetryStrategy = new ExponentialBackoff( RetryCount, @@ -27,11 +30,11 @@ class Program TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(4)); - static long MessageIdCounter = 0; + static long messageIdCounter = 0; static async Task Main() { - Microsoft.Extensions.Logging.ILogger logger = InitLogger().CreateLogger("loadgen"); + ILogger logger = InitLogger().CreateLogger("loadgen"); Log.Information($"Starting load run with the following settings:\r\n{Settings.Current.ToString()}"); try @@ -66,11 +69,9 @@ static async Task Main() () => GenTwinUpdate(client)); timers.Start(); - ( - CancellationTokenSource cts, + (CancellationTokenSource cts, ManualResetEventSlim completed, - Option handler - ) = ShutdownHandler.Init(TimeSpan.FromSeconds(5), logger); + Option handler) = ShutdownHandler.Init(TimeSpan.FromSeconds(5), logger); Log.Information("Load gen running."); @@ -112,7 +113,7 @@ static async void GenMessage( try { var message = new Message(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(messageBody))); - sequenceNumber = Interlocked.Increment(ref MessageIdCounter); + sequenceNumber = Interlocked.Increment(ref messageIdCounter); message.Properties.Add("sequenceNumber", sequenceNumber.ToString()); message.Properties.Add("batchId", batchId.ToString()); await client.SendEventAsync(Settings.Current.OutputName, message).ConfigureAwait(false); @@ -121,14 +122,13 @@ static async void GenMessage( { Log.Error($"Sequence number {sequenceNumber}, BatchId: {batchId.ToString()} {e}"); } - } } static async void GenTwinUpdate(ModuleClient client) { var twin = new TwinCollection(); - twin["messagesSent"] = MessageIdCounter; + twin["messagesSent"] = messageIdCounter; await client.UpdateReportedPropertiesAsync(twin).ConfigureAwait(false); } @@ -159,6 +159,7 @@ ITransportSettings[] GetTransportSettings() return new ITransportSettings[] { new AmqpTransportSettings(TransportType.Amqp_Tcp_Only) }; } } + ITransportSettings[] settings = GetTransportSettings(); ModuleClient moduleClient = await ModuleClient.CreateFromEnvironmentAsync(settings).ConfigureAwait(false); diff --git a/edge-modules/load-gen/Settings.cs b/edge-modules/load-gen/Settings.cs index f29a8f608af..351a9c03fa5 100644 --- a/edge-modules/load-gen/Settings.cs +++ b/edge-modules/load-gen/Settings.cs @@ -2,8 +2,6 @@ namespace LoadGen { using System; - using System.Collections.Generic; - using System.Collections.Immutable; using System.IO; using Microsoft.Azure.Devices.Client; using Microsoft.Extensions.Configuration; @@ -14,38 +12,25 @@ namespace LoadGen [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] public class Settings { - public TimeSpan MessageFrequency { get; } - - public double JitterFactor { get; } - - public TimeSpan TwinUpdateFrequency { get; } - - public ulong MessageSizeInBytes { get; } - - [JsonConverter(typeof(StringEnumConverter))] - public TransportType TransportType { get; } - - public string OutputName { get; } - - private static readonly Lazy defaultSettings = new Lazy(() => - { - IConfiguration configuration = new ConfigurationBuilder() + static readonly Lazy defaultSettings = new Lazy( + () => + { + IConfiguration configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("config/settings.json", optional: true) .AddEnvironmentVariables() .Build(); - return new Settings( - configuration.GetValue("messageFrequency", TimeSpan.FromMilliseconds(20)), - configuration.GetValue("jitterFactor", 0.5), - configuration.GetValue("twinUpdateFrequency", TimeSpan.FromMilliseconds(500)), - configuration.GetValue("messageSizeInBytes", 1024), - configuration.GetValue("transportType", TransportType.Amqp_Tcp_Only), - configuration.GetValue("outputName", "output1") - ); - }); + return new Settings( + configuration.GetValue("messageFrequency", TimeSpan.FromMilliseconds(20)), + configuration.GetValue("jitterFactor", 0.5), + configuration.GetValue("twinUpdateFrequency", TimeSpan.FromMilliseconds(500)), + configuration.GetValue("messageSizeInBytes", 1024), + configuration.GetValue("transportType", TransportType.Amqp_Tcp_Only), + configuration.GetValue("outputName", "output1")); + }); - private Settings( + Settings( TimeSpan messageFrequency, double jitterFactor, TimeSpan twinUpdateFrequency, @@ -63,13 +48,23 @@ private Settings( public static Settings Current { - get - { - return Settings.defaultSettings.Value; - } + get { return defaultSettings.Value; } } - public override String ToString() + public TimeSpan MessageFrequency { get; } + + public double JitterFactor { get; } + + public TimeSpan TwinUpdateFrequency { get; } + + public ulong MessageSizeInBytes { get; } + + [JsonConverter(typeof(StringEnumConverter))] + public TransportType TransportType { get; } + + public string OutputName { get; } + + public override string ToString() { return JsonConvert.SerializeObject(this, Formatting.Indented); } diff --git a/edge-modules/load-gen/Timers.cs b/edge-modules/load-gen/Timers.cs index 5c290846cd7..20eefef37e4 100644 --- a/edge-modules/load-gen/Timers.cs +++ b/edge-modules/load-gen/Timers.cs @@ -7,34 +7,9 @@ namespace LoadGen public class Timers : IDisposable { - private List timerTasks = new List(); + readonly List timerTasks = new List(); - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - foreach (TimerTask task in this.timerTasks) - { - task.Timer.Dispose(); - } - } - - disposedValue = true; - } - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - } - #endregion + bool disposedValue = false; // To detect redundant calls public void Add(TimeSpan interval, double jitterFactor, Action callback) { @@ -57,14 +32,33 @@ public void Stop() task.Quit = true; } } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + this.Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + foreach (TimerTask task in this.timerTasks) + { + task.Timer.Dispose(); + } + } + + this.disposedValue = true; + } + } } class TimerTask { - public Action Callback { get; } - public Timer Timer { get; } - public bool Quit { get; set; } - public TimerTask(TimeSpan interval, double jitterFactor, Action callback) { this.Callback = callback; @@ -84,12 +78,18 @@ public TimerTask(TimeSpan interval, double jitterFactor, Action callback) if (this.Quit == false) { this.Timer.Enabled = false; - this.Timer.Interval = TimerTask.ApplyJitter(random, interval, jitterFactor).TotalMilliseconds; + this.Timer.Interval = ApplyJitter(random, interval, jitterFactor).TotalMilliseconds; this.Timer.Enabled = true; } }; } + public Action Callback { get; } + + public Timer Timer { get; } + + public bool Quit { get; set; } + static TimeSpan ApplyJitter(Random random, TimeSpan interval, double jitterFactor) { double sign = random.NextDouble() > 0.5 ? 1.0 : -1.0; diff --git a/edge-modules/load-gen/load-gen.csproj b/edge-modules/load-gen/load-gen.csproj index 6ce2d548c76..b139ce3667c 100644 --- a/edge-modules/load-gen/load-gen.csproj +++ b/edge-modules/load-gen/load-gen.csproj @@ -36,4 +36,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyDbStore.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyDbStore.cs index 39939efef75..43f3cfe9907 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyDbStore.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyDbStore.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb using System.Threading.Tasks; using App.Metrics; using App.Metrics.Timer; - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using RocksDbSharp; @@ -135,9 +134,25 @@ public Task IterateBatch(int batchSize, Func callback, Can return this.IterateBatch(iterator => iterator.SeekToFirst(), batchSize, callback, cancellationToken); } + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Don't dispose the Db here as we don't know if the caller + // meant to dispose just the ColumnFamilyDbStore or the DB. + // this.db?.Dispose(); + } + } + async Task IterateBatch(Action seeker, int batchSize, Func callback, CancellationToken cancellationToken) { - // Use tailing iterator to prevent creating a snapshot. + // Use tailing iterator to prevent creating a snapshot. var readOptions = new ReadOptions(); readOptions.SetTailing(true); @@ -171,30 +186,14 @@ static class Metrics RateUnit = TimeUnit.Seconds }; - static MetricTags GetTags(string id) - { - return new MetricTags("EndpointId", id); - } - - public static IDisposable DbPutLatency(string identity) => Edge.Util.Metrics.Latency(GetTags(identity), DbPutLatencyOptions); + public static IDisposable DbPutLatency(string identity) => Util.Metrics.Latency(GetTags(identity), DbPutLatencyOptions); - public static IDisposable DbGetLatency(string identity) => Edge.Util.Metrics.Latency(GetTags(identity), DbGetLatencyOptions); - } + public static IDisposable DbGetLatency(string identity) => Util.Metrics.Latency(GetTags(identity), DbGetLatencyOptions); - protected virtual void Dispose(bool disposing) - { - if (disposing) + static MetricTags GetTags(string id) { - // Don't dispose the Db here as we don't know if the caller - // meant to dispose just the ColumnFamilyDbStore or the DB. - //this.db?.Dispose(); + return new MetricTags("EndpointId", id); } } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyStorageRocksDbWrapper.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyStorageRocksDbWrapper.cs index eb2a2d21b29..f03936505cb 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyStorageRocksDbWrapper.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/ColumnFamilyStorageRocksDbWrapper.cs @@ -10,7 +10,7 @@ namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb using RocksDbSharp; /// - /// Wrapper around RocksDb. This is mainly needed because each ColumnFamilyDbStore contains an instance of this object, + /// Wrapper around RocksDb. This is mainly needed because each ColumnFamilyDbStore contains an instance of this object, /// and hence it could get disposed multiple times. This class makes sure the underlying RocksDb instance is disposed only once. /// /// Because of an issue where ListColumnFamilies does not return an accurate list of column families on Linux, @@ -137,7 +137,7 @@ public void RemoveColumnFamily(string columnFamilyName) public void AddColumnFamily(string entityName) => File.AppendAllLines(this.columnFamiliesFilePath, new List { entityName }); - public IEnumerable ListColumnFamilies() => (File.Exists(this.columnFamiliesFilePath)) ? File.ReadAllLines(this.columnFamiliesFilePath).ToList() : new List { DefaultPartitionName }; + public IEnumerable ListColumnFamilies() => File.Exists(this.columnFamiliesFilePath) ? File.ReadAllLines(this.columnFamiliesFilePath).ToList() : new List { DefaultPartitionName }; } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/DbStoreProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/DbStoreProvider.cs index 5bdef98cc03..433e39b11b3 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/DbStoreProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/DbStoreProvider.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; using RocksDbSharp; @@ -18,7 +17,7 @@ public class DbStoreProvider : IDbStoreProvider readonly IRocksDb db; readonly ConcurrentDictionary entityDbStoreDictionary; - readonly Timer compactionTimer; //TODO: Bug logged to be fixed to proper dispose and test. + readonly Timer compactionTimer; // TODO: Bug logged to be fixed to proper dispose and test. DbStoreProvider(IRocksDbOptionsProvider optionsProvider, IRocksDb db, IDictionary entityDbStoreDictionary) { @@ -28,19 +27,6 @@ public class DbStoreProvider : IDbStoreProvider this.compactionTimer = new Timer(this.RunCompaction, null, CompactionPeriod, CompactionPeriod); } - void RunCompaction(object state) - { - Events.StartingCompaction(); - foreach (KeyValuePair entityDbStore in this.entityDbStoreDictionary) - { - if (entityDbStore.Value is ColumnFamilyDbStore cfDbStore) - { - Events.CompactingStore(entityDbStore.Key); - this.db.Compact(cfDbStore.Handle); - } - } - } - public static DbStoreProvider Create(IRocksDbOptionsProvider optionsProvider, string path, IEnumerable partitionsList) { IRocksDb db = RocksDbWrapper.Create(optionsProvider, path, partitionsList); @@ -85,6 +71,12 @@ public void RemoveDbStore(string partitionName) } } + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) { if (disposing) @@ -94,18 +86,24 @@ protected virtual void Dispose(bool disposing) } } - public void Dispose() + void RunCompaction(object state) { - this.Dispose(true); - GC.SuppressFinalize(this); + Events.StartingCompaction(); + foreach (KeyValuePair entityDbStore in this.entityDbStoreDictionary) + { + if (entityDbStore.Value is ColumnFamilyDbStore cfDbStore) + { + Events.CompactingStore(entityDbStore.Key); + this.db.Compact(cfDbStore.Handle); + } + } } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); - // Use an ID not used by other components const int IdStart = 4000; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/Microsoft.Azure.Devices.Edge.Storage.RocksDb.csproj b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/Microsoft.Azure.Devices.Edge.Storage.RocksDb.csproj index 1ccd9766f0e..e8cd4b34bee 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/Microsoft.Azure.Devices.Edge.Storage.RocksDb.csproj +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/Microsoft.Azure.Devices.Edge.Storage.RocksDb.csproj @@ -36,4 +36,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbOptionsProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbOptionsProvider.cs index d7f841f1a63..f82bac087f2 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbOptionsProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbOptionsProvider.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb { - using RocksDbSharp; using Microsoft.Azure.Devices.Edge.Util; + using RocksDbSharp; public class RocksDbOptionsProvider : IRocksDbOptionsProvider { @@ -16,33 +16,28 @@ public class RocksDbOptionsProvider : IRocksDbOptionsProvider // Amount of data to build up in memory (backed by an unsorted log // on disk) before converting to a sorted on-disk file. // Set to limit total memory usage. - // const ulong WriteBufferSize = 2 * 1024 * 1024; - // + // target_file_size_base = 2M (bytes) per column family // Target file size for compaction // Set to limit "max_compaction_bytes" and file sizes - // const ulong TargetFileSizeBase = 2UL * 1024UL * 1024UL; - // + // max_bytes_for_level_base = 10M (bytes) per column family // Control maximum total data size for a level. // Set to limit file sizes - // const ulong MaxBytesForLevelBase = 10UL * 1024UL * 1024UL; - // + // soft_pending_compaction_bytes_limit = 10M (bytes) per column family. // All writes will be slowed down to at least delayed_write_rate if estimated // bytes needed to be compaction exceed this threshold // Set to approx 1/10 hard limit - // const ulong SoftPendingCompactionBytes = 10UL * 1024UL * 1024UL; - // + // hard_pending_compaction_bytes_limit = 1G (bytes) per column family // All writes are stopped if estimated bytes needed to be compaction exceed // this threshold. // Set to a limit less than a 32 bit # - // const ulong HardPendingCompactionBytes = 1024UL * 1024UL * 1024UL; readonly ISystemEnvironment env; @@ -86,6 +81,7 @@ public ColumnFamilyOptions GetColumnFamilyOptions() options.SetSoftPendingCompactionBytesLimit(SoftPendingCompactionBytes); options.SetHardPendingCompactionBytesLimit(HardPendingCompactionBytes); } + return options; } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbWrapper.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbWrapper.cs index 091d1bb850c..90222758f8b 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbWrapper.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage.RocksDb/RocksDbWrapper.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb using RocksDbSharp; /// - /// Wrapper around RocksDb. This is mainly needed because each ColumnFamilyDbStore contains an instance of this object, + /// Wrapper around RocksDb. This is mainly needed because each ColumnFamilyDbStore contains an instance of this object, /// and hence it could get disposed multiple times. This class makes sure the underlying RocksDb instance is disposed only once. /// sealed class RocksDbWrapper : IRocksDb @@ -48,23 +48,6 @@ public static RocksDbWrapper Create(IRocksDbOptionsProvider optionsProvider, str public IEnumerable ListColumnFamilies() => ListColumnFamilies(this.dbOptions, this.path); - static IEnumerable ListColumnFamilies(DbOptions dbOptions, string path) - { - Preconditions.CheckNonWhiteSpace(path, nameof(path)); - // ListColumnFamilies will throw if the DB doesn't exist yet, so wrap it in a try catch. - IEnumerable columnFamilies = null; - try - { - columnFamilies = RocksDb.ListColumnFamilies(dbOptions, path); - } - catch - { - // ignored since ListColumnFamilies will throw if the DB doesn't exist yet. - } - - return columnFamilies ?? Enumerable.Empty(); - } - public ColumnFamilyHandle GetColumnFamily(string columnFamilyName) => this.db.GetColumnFamily(columnFamilyName); public ColumnFamilyHandle CreateColumnFamily(ColumnFamilyOptions columnFamilyOptions, string entityName) => this.db.CreateColumnFamily(columnFamilyOptions, entityName); @@ -88,5 +71,22 @@ public void Dispose() this.db?.Dispose(); } } + + static IEnumerable ListColumnFamilies(DbOptions dbOptions, string path) + { + Preconditions.CheckNonWhiteSpace(path, nameof(path)); + // ListColumnFamilies will throw if the DB doesn't exist yet, so wrap it in a try catch. + IEnumerable columnFamilies = null; + try + { + columnFamilies = RocksDb.ListColumnFamilies(dbOptions, path); + } + catch + { + // ignored since ListColumnFamilies will throw if the DB doesn't exist yet. + } + + return columnFamilies ?? Enumerable.Empty(); + } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/AssemblyInfo.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/AssemblyInfo.cs index a7734a1e227..8129b419abe 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/AssemblyInfo.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/AssemblyInfo.cs @@ -4,4 +4,3 @@ [assembly: InternalsVisibleTo("Microsoft.Azure.Devices.Edge.Storage.Test")] [assembly: InternalsVisibleTo("Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test")] [assembly: InternalsVisibleTo("Microsoft.Azure.Devices.Edge.Hub.Core.Test")] - diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EncryptedStore.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EncryptedStore.cs index 89881d0bade..391f34785fe 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EncryptedStore.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EncryptedStore.cs @@ -107,6 +107,12 @@ public Task IterateBatch(TK startKey, int batchSize, Func perEntit cancellationToken); } + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) { if (disposing) @@ -114,11 +120,5 @@ protected virtual void Dispose(bool disposing) this.entityStore?.Dispose(); } } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EntityStore.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EntityStore.cs index faa0878f136..09d9d4358fa 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EntityStore.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/EntityStore.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Devices.Edge.Storage /// 1. FindOrPut/PutOrUpdate support /// 2. Serialized writes for the same key /// 3. Support for generic types for keys and values - /// TODO - Since Key/Value types are generic, need to look into the right behavior to handle null values here. + /// TODO - Since Key/Value types are generic, need to look into the right behavior to handle null values here. /// public class EntityStore : IEntityStore { @@ -163,6 +163,23 @@ public Task IterateBatch(TK startKey, int batchSize, Func callback public Task IterateBatch(int batchSize, Func callback, CancellationToken cancellationToken) => this.IterateBatch(Option.None(), batchSize, callback, cancellationToken); + public Task Contains(TK key, CancellationToken cancellationToken) + => this.dbStore.Contains(key.ToBytes(), cancellationToken); + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.dbStore?.Dispose(); + } + } + Task IterateBatch(Option startKey, int batchSize, Func callback, CancellationToken cancellationToken) { Preconditions.CheckRange(batchSize, 1, nameof(batchSize)); @@ -179,22 +196,5 @@ Task DeserializingCallback(byte[] keyBytes, byte[] valueBytes) k => this.dbStore.IterateBatch(k.ToBytes(), batchSize, DeserializingCallback, cancellationToken), () => this.dbStore.IterateBatch(batchSize, DeserializingCallback, cancellationToken)); } - - public Task Contains(TK key, CancellationToken cancellationToken) - => this.dbStore.Contains(key.ToBytes(), cancellationToken); - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - this.dbStore?.Dispose(); - } - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/ISequentialStore.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/ISequentialStore.cs index 45aa6e851b8..ef568d0c3ac 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/ISequentialStore.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/ISequentialStore.cs @@ -8,8 +8,8 @@ namespace Microsoft.Azure.Devices.Edge.Storage /// /// Store for storing entities in an ordered list - Entities can be retrieved in the same order in which they were saved. - /// This can be used for implementing queues. - /// Each saved entity is associated with an offset, which can be used to retrieve the entity. + /// This can be used for implementing queues. + /// Each saved entity is associated with an offset, which can be used to retrieve the entity. /// public interface ISequentialStore : IDisposable { diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/IStoreProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/IStoreProvider.cs index 3c91ff72ec4..50a9cb52887 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/IStoreProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/IStoreProvider.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage { using System; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; /// /// Provides stores that are higher level abstractions over the underlying key/value stores. diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/InMemoryDbStore.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/InMemoryDbStore.cs index 430f275cb4d..0e8d6099e71 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/InMemoryDbStore.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/InMemoryDbStore.cs @@ -75,19 +75,6 @@ public async Task IterateBatch(byte[] startKey, int batchSize, Func snapshot, int index, int batchSize, Func callback, CancellationToken cancellationToken) - { - if (index >= 0) - { - for (int i = index; i < index + batchSize && i < snapshot.Count && !cancellationToken.IsCancellationRequested; i++) - { - var keyClone = snapshot[i].key.Clone() as byte[]; - var valueClone = snapshot[i].value.Clone() as byte[]; - await callback(keyClone, valueClone); - } - } - } - public async Task> GetFirstEntry(CancellationToken cancellationToken) { using (await this.listLock.ReaderLockAsync(cancellationToken)) @@ -133,6 +120,24 @@ public async Task Remove(byte[] key, CancellationToken cancellationToken) } } + public void Dispose() + { + // No-op + } + + async Task IterateBatch(List<(byte[] key, byte[] value)> snapshot, int index, int batchSize, Func callback, CancellationToken cancellationToken) + { + if (index >= 0) + { + for (int i = index; i < index + batchSize && i < snapshot.Count && !cancellationToken.IsCancellationRequested; i++) + { + var keyClone = snapshot[i].key.Clone() as byte[]; + var valueClone = snapshot[i].value.Clone() as byte[]; + await callback(keyClone, valueClone); + } + } + } + async Task> GetSnapshot(CancellationToken cancellationToken) { using (await this.listLock.ReaderLockAsync(cancellationToken)) @@ -141,23 +146,20 @@ public async Task Remove(byte[] key, CancellationToken cancellationToken) } } - public void Dispose() + class ByteArrayComparer : IEqualityComparer { - // No-op - } + public bool Equals(byte[] x, byte[] y) => (x == null && y == null) || x.SequenceEqual(y); - class ItemKeyedCollection : KeyedCollection - { - public ItemKeyedCollection(IEqualityComparer keyEqualityComparer) - : base(keyEqualityComparer) + public int GetHashCode(byte[] obj) { - } - - protected override byte[] GetKeyForItem(Item item) => item.Key; + int hashCode = 1291371069; + foreach (byte b in obj) + { + hashCode = hashCode * -1521134295 + b.GetHashCode(); + } - public IList<(byte[], byte[])> ItemList => this.Items - .Select(i => (i.Key, i.Value)) - .ToList(); + return hashCode; + } } class Item @@ -173,20 +175,18 @@ public Item(byte[] key, byte[] value) public byte[] Value { get; set; } } - class ByteArrayComparer : IEqualityComparer + class ItemKeyedCollection : KeyedCollection { - public bool Equals(byte[] x, byte[] y) => (x == null && y == null) || x.SequenceEqual(y); - - public int GetHashCode(byte[] obj) + public ItemKeyedCollection(IEqualityComparer keyEqualityComparer) + : base(keyEqualityComparer) { - int hashCode = 1291371069; - foreach (byte b in obj) - { - hashCode = hashCode * -1521134295 + b.GetHashCode(); - } - - return hashCode; } + + public IList<(byte[], byte[])> ItemList => this.Items + .Select(i => (i.Key, i.Value)) + .ToList(); + + protected override byte[] GetKeyForItem(Item item) => item.Key; } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/Microsoft.Azure.Devices.Edge.Storage.csproj b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/Microsoft.Azure.Devices.Edge.Storage.csproj index 550f612b234..ea76fda3693 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/Microsoft.Azure.Devices.Edge.Storage.csproj +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/Microsoft.Azure.Devices.Edge.Storage.csproj @@ -27,4 +27,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/SequentialStore.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/SequentialStore.cs index 430f6fb8d7b..38c1e72b6f9 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/SequentialStore.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/SequentialStore.cs @@ -11,8 +11,8 @@ namespace Microsoft.Azure.Devices.Edge.Storage /// /// Store for storing entities in an ordered list - Entities can be retrieved in the same order in which they were saved. - /// This can be used for implementing queues. - /// Each saved entity is associated with an offset, which can be used to retrieve the entity. + /// This can be used for implementing queues. + /// Each saved entity is associated with an offset, which can be used to retrieve the entity. /// class SequentialStore : ISequentialStore { @@ -32,12 +32,6 @@ class SequentialStore : ISequentialStore public string EntityName => this.entityStore.EntityName; - public Task Append(T item) => this.Append(item, CancellationToken.None); - - public Task RemoveFirst(Func> predicate) => this.RemoveFirst(predicate, CancellationToken.None); - - public Task> GetBatch(long startingOffset, int batchSize) => this.GetBatch(startingOffset, batchSize, CancellationToken.None); - public static Task> Create(IEntityStore entityStore) => Create(entityStore, DefaultHeadOffset); @@ -53,6 +47,12 @@ public static async Task> Create(IEntityStore ent return sequentialStore; } + public Task Append(T item) => this.Append(item, CancellationToken.None); + + public Task RemoveFirst(Func> predicate) => this.RemoveFirst(predicate, CancellationToken.None); + + public Task> GetBatch(long startingOffset, int batchSize) => this.GetBatch(startingOffset, batchSize, CancellationToken.None); + public async Task Append(T item, CancellationToken cancellationToken) { using (await this.tailLockObject.LockAsync(cancellationToken)) @@ -69,7 +69,7 @@ public async Task RemoveFirst(Func> predicate, Cancell { using (await this.headLockObject.LockAsync(cancellationToken)) { - // Tail offset could change here, but not holding a lock for efficiency. + // Tail offset could change here, but not holding a lock for efficiency. if (this.IsEmpty()) { return false; @@ -124,6 +124,12 @@ await this.entityStore.IterateBatch( return batch; } + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) { if (disposing) @@ -132,12 +138,6 @@ protected virtual void Dispose(bool disposing) } } - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - bool IsEmpty() => this.headOffset > this.tailOffset; } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/StoreProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/StoreProvider.cs index ec97b550206..3a4ecd68df0 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/StoreProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Storage/StoreProvider.cs @@ -56,6 +56,8 @@ public Task RemoveStore(IEntityStore entityStore) return Task.CompletedTask; } + public void Dispose() => this.Dispose(true); + protected virtual void Dispose(bool disposing) { if (disposing) @@ -63,7 +65,5 @@ protected virtual void Dispose(bool disposing) this.dbStoreProvider?.Dispose(); } } - - public void Dispose() => this.Dispose(true); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/AsyncLockProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/AsyncLockProvider.cs index ed548dce98a..c0992ad0a2a 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/AsyncLockProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/AsyncLockProvider.cs @@ -5,7 +5,7 @@ namespace Microsoft.Azure.Devices.Edge.Util using Microsoft.Azure.Devices.Edge.Util.Concurrency; /// - /// Provides locks for keys. Keys are divided into n shards and there is one lock per shard. + /// Provides locks for keys. Keys are divided into n shards and there is one lock per shard. /// This improves performance as keys from different shards are locked on separate locks /// public class AsyncLockProvider @@ -19,6 +19,7 @@ public AsyncLockProvider(int keyShardCount) { throw new ArgumentException("KeyShardCount should be > 0"); } + this.keyShardCount = keyShardCount; this.locks = new AsyncLock[keyShardCount]; for (int i = 0; i < keyShardCount; i++) @@ -33,6 +34,7 @@ public AsyncLock GetLock(T key) { throw new ArgumentNullException(nameof(key)); } + int index = Math.Abs(key.GetHashCode() % this.keyShardCount); return this.locks[index]; } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CertificateHelper.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CertificateHelper.cs index 1b708229a23..1a03b6138d0 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CertificateHelper.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CertificateHelper.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Devices.Edge.Util using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; + using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Edged; using Microsoft.Azure.Devices.Edge.Util.Edged.GeneratedCode; @@ -16,6 +17,7 @@ namespace Microsoft.Azure.Devices.Edge.Util using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; + using X509Certificate = Org.BouncyCastle.X509.X509Certificate; public static class CertificateHelper { @@ -29,23 +31,17 @@ public static string GetSha256Thumbprint(X509Certificate2 cert) } } - static string ToHexString(byte[] bytes) - { - Preconditions.CheckNotNull(bytes); - return BitConverter.ToString(bytes).Replace("-", string.Empty); - } - public static (IList, Option) BuildCertificateList(X509Certificate2 cert, Option> additionalCACertificates) { var chain = new X509Chain { ChainPolicy = { - //For performance reasons do not check revocation status. + // For performance reasons do not check revocation status. RevocationMode = X509RevocationMode.NoCheck, - //Does not check revocation status of the root certificate (sounds like it is meaningless with the option above - ask Simon or Alex) + // Does not check revocation status of the root certificate (sounds like it is meaningless with the option above - ask Simon or Alex) RevocationFlag = X509RevocationFlag.ExcludeRoot, - //Certificate Authority can be unknown if it is not issued directly by a well-known CA + // Certificate Authority can be unknown if it is not issued directly by a well-known CA VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority } }; @@ -100,50 +96,29 @@ public static (bool, Option) ValidateCert(X509Certificate2 remoteCertifi } (bool, Option) result = trustedCACerts.Map( - caList => - { - bool match = false; - foreach (X509Certificate2 chainElement in remoteCerts) + caList => { - string thumbprint = GetSha256Thumbprint(chainElement); - if (remoteCertificateChain.Any(cert => GetSha256Thumbprint(cert) == thumbprint) && - caList.Any(cert => GetSha256Thumbprint(cert) == thumbprint)) + bool match = false; + foreach (X509Certificate2 chainElement in remoteCerts) { - match = true; - break; + string thumbprint = GetSha256Thumbprint(chainElement); + if (remoteCertificateChain.Any(cert => GetSha256Thumbprint(cert) == thumbprint) && + caList.Any(cert => GetSha256Thumbprint(cert) == thumbprint)) + { + match = true; + break; + } } - } - return match - ? (true, Option.None()) - : (false, Option.Some($"Error validating cert with Subject: {remoteCertificate.SubjectName} Thumbprint: {GetSha256Thumbprint(remoteCertificate)}")); - }) + return match + ? (true, Option.None()) + : (false, Option.Some($"Error validating cert with Subject: {remoteCertificate.SubjectName} Thumbprint: {GetSha256Thumbprint(remoteCertificate)}")); + }) .GetOrElse(() => (true, Option.None())); return result; } - static Option GetCommonNameFromSubject(string subject) - { - Option commonName = Option.None(); - string[] parts = subject.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - foreach (string part in parts) - { - string partTrimed = part.Trim(); - if (partTrimed.StartsWith("CN", StringComparison.OrdinalIgnoreCase)) - { - string[] cnParts = partTrimed.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); - if (cnParts.Length > 1) - { - commonName = Option.Some(cnParts[1].Trim()); - } - } - } - - return commonName; - } - public static bool ValidateCommonName(X509Certificate2 certificate, string commonName) { Preconditions.CheckNotNull(certificate); @@ -261,7 +236,7 @@ public static IEnumerable ExtractCertsFromPem(string certPath) public static IEnumerable GetCertificatesFromPem(IEnumerable rawPemCerts) => rawPemCerts - .Select(c => System.Text.Encoding.UTF8.GetBytes(c)) + .Select(c => Encoding.UTF8.GetBytes(c)) .Select(c => new X509Certificate2(c)) .ToList(); @@ -383,18 +358,20 @@ internal static (X509Certificate2, IEnumerable) ParseCertifica object certObject = pemReader.ReadObject(); while (certObject != null) { - if (certObject is Org.BouncyCastle.X509.X509Certificate x509Cert) + if (certObject is X509Certificate x509Cert) { chain.Add(new X509CertificateEntry(x509Cert)); } + // when processing certificates generated via openssl certObject type is of AsymmetricCipherKeyPair if (certObject is AsymmetricCipherKeyPair) { certObject = ((AsymmetricCipherKeyPair)certObject).Private; } + if (certObject is RsaPrivateCrtKeyParameters) { - keyParams = ((RsaPrivateCrtKeyParameters)certObject); + keyParams = (RsaPrivateCrtKeyParameters)certObject; } certObject = pemReader.ReadObject(); @@ -414,5 +391,32 @@ internal static (X509Certificate2, IEnumerable) ParseCertifica return (cert, certsChain); } } + + static string ToHexString(byte[] bytes) + { + Preconditions.CheckNotNull(bytes); + return BitConverter.ToString(bytes).Replace("-", string.Empty); + } + + static Option GetCommonNameFromSubject(string subject) + { + Option commonName = Option.None(); + string[] parts = subject.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string part in parts) + { + string partTrimed = part.Trim(); + if (partTrimed.StartsWith("CN", StringComparison.OrdinalIgnoreCase)) + { + string[] cnParts = partTrimed.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (cnParts.Length > 1) + { + commonName = Option.Some(cnParts[1].Trim()); + } + } + } + + return commonName; + } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CollectionEx.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CollectionEx.cs index 503f2984727..374d10f09db 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CollectionEx.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/CollectionEx.cs @@ -32,6 +32,7 @@ public static Option HeadOption(this IEnumerable src) } } } + return Option.None(); } @@ -102,6 +103,7 @@ public static bool TryRemove(this IDictionary dictio dictionary.Remove(key); return true; } + return false; } @@ -126,6 +128,7 @@ public static bool TryGetNonEmptyValue(this IDictionary : IEqualityComparer x, IDictionary y) => ReferenceEquals(x, y) || - ( - x != null && - y != null && - x.Keys.Count() == y.Keys.Count() && - x.Keys.All( - key => y.ContainsKey(key) && - ( - (x[key] == null && y[key] == null) || - ( - x[key] != null && - x[key].Equals(y[key]) - ) - ) - ) - ); + (x != null && + y != null && + x.Keys.Count() == y.Keys.Count() && + x.Keys.All( + key => y.ContainsKey(key) && + ((x[key] == null && y[key] == null) || + (x[key] != null && + x[key].Equals(y[key]))))); public int GetHashCode(IDictionary obj) => obj.Aggregate(17, (acc, pair) => (acc * 31 + pair.Key.GetHashCode()) * 31 + pair.Value.GetHashCode()); @@ -35,21 +28,14 @@ public class ReadOnlyDictionaryComparer : IEqualityComparer x, IReadOnlyDictionary y) => ReferenceEquals(x, y) || - ( - x != null && - y != null && - x.Keys.Count() == y.Keys.Count() && - x.Keys.All( - key => y.ContainsKey(key) && - ( - (x[key] == null && y[key] == null) || - ( - x[key] != null && - x[key].Equals(y[key]) - ) - ) - ) - ); + (x != null && + y != null && + x.Keys.Count() == y.Keys.Count() && + x.Keys.All( + key => y.ContainsKey(key) && + ((x[key] == null && y[key] == null) || + (x[key] != null && + x[key].Equals(y[key]))))); public int GetHashCode(IReadOnlyDictionary obj) => obj.Aggregate(17, (acc, pair) => (acc * 31 + pair.Key.GetHashCode()) * 31 + pair.Value.GetHashCode()); diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/DiskFile.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/DiskFile.cs index a09730c8a8b..9af2e90aac8 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/DiskFile.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/DiskFile.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Devices.Edge.Util public static class DiskFile { - static TimeSpan DefaultOperationTimeout = TimeSpan.FromSeconds(60); + static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromSeconds(60); public static Task ReadAllAsync(string path) => ReadAllAsync(path, DefaultOperationTimeout); diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ExceptionEx.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ExceptionEx.cs index 331549f8fed..01d73b5b9bf 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ExceptionEx.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ExceptionEx.cs @@ -73,7 +73,6 @@ public static T UnwindAs(this Exception exception) public static bool HasTimeoutException(this Exception ex) => ex != null && (ex is TimeoutException || HasTimeoutException(ex.InnerException) || - (ex is AggregateException argEx && (argEx.InnerExceptions?.Select(e => HasTimeoutException(e)).Any(e => e) ?? false))); - + (ex is AggregateException argEx && (argEx.InnerExceptions?.Select(e => HasTimeoutException(e)).Any(e => e) ?? false))); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Fallback.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Fallback.cs index 2c28efe95c5..bd21776806e 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Fallback.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Fallback.cs @@ -25,7 +25,13 @@ public static Task> ExecuteAsync(Func> primary, Func> public static Task> ExecuteAsync(params Func[] options) { Preconditions.CheckNotNull(options, nameof(options)); - return ExecuteAsync(options.Select, Func>>(o => (async () => { await o(); return true; })).ToArray()); + return ExecuteAsync( + options.Select, Func>>( + o => (async () => + { + await o(); + return true; + })).ToArray()); } public static async Task> ExecuteAsync(params Func>[] options) @@ -45,6 +51,7 @@ public static async Task> ExecuteAsync(params Func>[] options) exceptions.Add(ex); } } + return Try.Failure(new AggregateException(exceptions)); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/HttpClientHelper.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/HttpClientHelper.cs index 35dba11568c..d674c7f025f 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/HttpClientHelper.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/HttpClientHelper.cs @@ -8,9 +8,9 @@ namespace Microsoft.Azure.Devices.Edge.Util public class HttpClientHelper { - private const string HttpScheme = "http"; - private const string HttpsScheme = "https"; - private const string UnixScheme = "unix"; + const string HttpScheme = "http"; + const string HttpsScheme = "https"; + const string UnixScheme = "unix"; public static HttpClient GetHttpClient(Uri serverUri) { diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/JsonEx.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/JsonEx.cs index 476a96fc894..9cc55d778b9 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/JsonEx.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/JsonEx.cs @@ -11,12 +11,26 @@ namespace Microsoft.Azure.Devices.Edge.Util public static class JsonEx { + static readonly JTokenType[] ValidDiffTypes = + { + JTokenType.Boolean, + JTokenType.Float, + JTokenType.Integer, + JTokenType.Null, + JTokenType.Object, + JTokenType.String, + JTokenType.Date + }; + + static readonly string[] MetadataPropertyNames = { "$metadata", "$version" }; + public static T Get(this JObject obj, string key) { if (!obj.TryGetValue(key, StringComparison.OrdinalIgnoreCase, out JToken token)) { throw new JsonSerializationException($"Could not find {key} in JObject."); } + return token.Value(); } @@ -67,20 +81,10 @@ public static JToken Merge(JToken baselineToken, JToken patchToken, bool treatNu throw new InvalidOperationException($"Property {patchProp.Name} has a value of unsupported type. Valid types are integer, float, string, bool, null and nested object"); } } + return result; } - static readonly JTokenType[] ValidDiffTypes = - { - JTokenType.Boolean, - JTokenType.Float, - JTokenType.Integer, - JTokenType.Null, - JTokenType.Object, - JTokenType.String, - JTokenType.Date - }; - public static bool IsValidToken(JToken token) => ValidDiffTypes.Any(t => t == token.Type); public static string Diff(object from, object to) @@ -166,8 +170,6 @@ public static JObject Diff(JToken fromToken, JToken toToken) return patch; } - static readonly string[] MetadataPropertyNames = { "$metadata", "$version" }; - public static JToken StripMetadata(JToken token) { // get rid of metadata from the token in case it has any because we don't want @@ -243,6 +245,8 @@ public IEnumerator GetEnumerator() } } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + void Validate(string strNum, uint expectedNum) { // The zero-th item should have an empty num @@ -266,8 +270,6 @@ void Validate(string strNum, uint expectedNum) } } } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/LinqEx.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/LinqEx.cs index 4ac4dfb284c..41dc389d290 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/LinqEx.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/LinqEx.cs @@ -46,8 +46,7 @@ public static IEnumerable IgnoreExceptions(this IEnumerable /// public static IEnumerable RemoveIntersectionKeys( this IEnumerable first, - IEnumerable second - ) => first.Except(second, StringKeyComparer.DefaultStringKeyComparer); + IEnumerable second) => first.Except(second, StringKeyComparer.DefaultStringKeyComparer); /// /// Compares and and returns @@ -59,9 +58,7 @@ IEnumerable second public static IEnumerable RemoveIntersectionKeys( this IEnumerable first, IEnumerable second, - Func keySelector - ) => first.Except(second, new StringKeyComparer(keySelector)); - + Func keySelector) => first.Except(second, new StringKeyComparer(keySelector)); /// /// Converts an IEnumerable into an IEnumerable<(uint, typeparamref name="T")/>. @@ -81,12 +78,11 @@ Func keySelector } } - internal class StringKeyComparer : IEqualityComparer + class StringKeyComparer : IEqualityComparer { + internal static readonly StringKeyComparer DefaultStringKeyComparer = new StringKeyComparer(s => s.Split(new[] { '=' }, 2)[0]); readonly Func keySelector; - internal readonly static StringKeyComparer DefaultStringKeyComparer = new StringKeyComparer(s => s.Split(new[] { '=' }, 2)[0]); - internal StringKeyComparer(Func keySelector) { Preconditions.CheckNotNull(keySelector, nameof(keySelector)); diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Logger.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Logger.cs index 03b7654778a..001a686d7eb 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Logger.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Logger.cs @@ -5,26 +5,29 @@ namespace Microsoft.Azure.Devices.Edge.Util using System.Collections.Generic; using Microsoft.Extensions.Logging; using Serilog; - using Serilog.Events; using Serilog.Core; + using Serilog.Events; public static class Logger { public const string RuntimeLogLevelEnvKey = "RuntimeLogLevel"; - static readonly Dictionary LogLevelDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) + static readonly Dictionary LogLevelDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) { - {"verbose", LogEventLevel.Verbose}, - {"debug", LogEventLevel.Debug}, - {"info", LogEventLevel.Information}, - {"information", LogEventLevel.Information}, - {"warning", LogEventLevel.Warning}, - {"error", LogEventLevel.Error}, - {"fatal", LogEventLevel.Fatal} + { "verbose", LogEventLevel.Verbose }, + { "debug", LogEventLevel.Debug }, + { "info", LogEventLevel.Information }, + { "information", LogEventLevel.Information }, + { "warning", LogEventLevel.Warning }, + { "error", LogEventLevel.Error }, + { "fatal", LogEventLevel.Fatal } }; + static readonly Lazy LoggerLazy = new Lazy(() => GetLoggerFactory(), true); static LogEventLevel logLevel = LogEventLevel.Information; + public static ILoggerFactory Factory => LoggerLazy.Value; + public static void SetLogLevel(string level) { Preconditions.CheckNonWhiteSpace(level, nameof(level)); @@ -33,21 +36,16 @@ public static void SetLogLevel(string level) public static LogEventLevel GetLogLevel() => logLevel; - static readonly Lazy LoggerLazy = new Lazy(() => GetLoggerFactory(), true); - - public static ILoggerFactory Factory => LoggerLazy.Value; - static ILoggerFactory GetLoggerFactory() { var levelSwitch = new LoggingLevelSwitch(); levelSwitch.MinimumLevel = logLevel; Serilog.Core.Logger loggerConfig = new LoggerConfiguration() - .MinimumLevel.ControlledBy(levelSwitch) - .Enrich.FromLogContext() - .WriteTo.Console( - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] - {Message}{NewLine}{Exception}" - ) - .CreateLogger(); + .MinimumLevel.ControlledBy(levelSwitch) + .Enrich.FromLogContext() + .WriteTo.Console( + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] - {Message}{NewLine}{Exception}").CreateLogger(); + if (levelSwitch.MinimumLevel <= LogEventLevel.Debug) { // Overwrite with richer content if less then debug @@ -55,10 +53,9 @@ static ILoggerFactory GetLoggerFactory() .MinimumLevel.ControlledBy(levelSwitch) .Enrich.FromLogContext() .WriteTo.Console( - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:1}] - {Message}{NewLine}{Exception}" - ) - .CreateLogger(); + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:1}] - {Message}{NewLine}{Exception}").CreateLogger(); } + ILoggerFactory factory = new LoggerFactory() .AddSerilog(loggerConfig); diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Metrics.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Metrics.cs index d25508cc329..1221944ced8 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Metrics.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Metrics.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Devices.Edge.Util public static class Metrics { - private static object gaugeListLock; + static readonly object gaugeListLock; static Metrics() { @@ -23,16 +23,9 @@ static Metrics() gaugeListLock = new object(); } - class NullDisposable : IDisposable - { - public void Dispose() - { - } - } - - public static Option MetricsCollector { private set; get; } + public static Option MetricsCollector { get; private set; } - private static List Gauges { set; get; } + static List Gauges { get; set; } public static void BuildMetricsCollector(IConfigurationRoot configuration) { @@ -55,8 +48,7 @@ public static void BuildMetricsCollector(IConfigurationRoot configuration) options.InfluxDb.Database = metricsDbName; options.InfluxDb.CreateDataBaseIfNotExists = true; options.FlushInterval = TimeSpan.FromSeconds(10); - } - ).Build(); + }).Build(); MetricsCollector = Option.Some(metricsCollector); StartReporting(metricsCollector); } @@ -72,8 +64,7 @@ public static void BuildMetricsCollector(IConfigurationRoot configuration) options.AppendMetricsToTextFile = appendToMetricsFile; options.FlushInterval = TimeSpan.FromSeconds(20); options.OutputPathAndFileName = metricsStoreLocation; - } - ).Build(); + }).Build(); MetricsCollector = Option.Some(metricsCollector); StartReporting(metricsCollector); } @@ -88,32 +79,13 @@ public static void RegisterGaugeCallback(Action callback) } } - static void StartReporting(IMetricsRoot metricsCollector) - { - // Start reporting metrics every 5s - var scheduler = new AppMetricsTaskScheduler( - TimeSpan.FromSeconds(5), - async () => - { - foreach (var callback in Gauges) - { - callback(); - } - await Task.WhenAll(metricsCollector.ReportRunner.RunAllAsync()); - }); - scheduler.Start(); - } - public static void CountIncrement(MetricTags tags, CounterOptions options, long amount) { Preconditions.CheckNotNull(tags); Preconditions.CheckNotNull(options); Preconditions.CheckNotNull(amount); - MetricsCollector.ForEach(mroot => - { - mroot.Measure.Counter.Increment(options, tags, amount); - }); + MetricsCollector.ForEach(mroot => { mroot.Measure.Counter.Increment(options, tags, amount); }); } public static void CountDecrement(MetricTags tags, CounterOptions options, long amount) @@ -122,10 +94,7 @@ public static void CountDecrement(MetricTags tags, CounterOptions options, long Preconditions.CheckNotNull(options); Preconditions.CheckNotNull(amount); - MetricsCollector.ForEach(mroot => - { - mroot.Measure.Counter.Decrement(options, tags, amount); - }); + MetricsCollector.ForEach(mroot => { mroot.Measure.Counter.Decrement(options, tags, amount); }); } public static void CountIncrement(CounterOptions options, long amount) @@ -133,10 +102,7 @@ public static void CountIncrement(CounterOptions options, long amount) Preconditions.CheckNotNull(options); Preconditions.CheckNotNull(amount); - MetricsCollector.ForEach(mroot => - { - mroot.Measure.Counter.Increment(options, amount); - }); + MetricsCollector.ForEach(mroot => { mroot.Measure.Counter.Increment(options, amount); }); } public static void CountDecrement(CounterOptions options, long amount) @@ -144,10 +110,7 @@ public static void CountDecrement(CounterOptions options, long amount) Preconditions.CheckNotNull(options); Preconditions.CheckNotNull(amount); - MetricsCollector.ForEach(mroot => - { - mroot.Measure.Counter.Decrement(options, amount); - }); + MetricsCollector.ForEach(mroot => { mroot.Measure.Counter.Decrement(options, amount); }); } public static IDisposable Latency(MetricTags tags, TimerOptions options) @@ -163,10 +126,31 @@ public static void SetGauge(GaugeOptions options, long amount) Preconditions.CheckNotNull(options); Preconditions.CheckNotNull(amount); - MetricsCollector.ForEach(mroot => + MetricsCollector.ForEach(mroot => { mroot.Measure.Gauge.SetValue(options, amount); }); + } + + static void StartReporting(IMetricsRoot metricsCollector) + { + // Start reporting metrics every 5s + var scheduler = new AppMetricsTaskScheduler( + TimeSpan.FromSeconds(5), + async () => + { + foreach (var callback in Gauges) + { + callback(); + } + + await Task.WhenAll(metricsCollector.ReportRunner.RunAllAsync()); + }); + scheduler.Start(); + } + + class NullDisposable : IDisposable + { + public void Dispose() { - mroot.Measure.Gauge.SetValue(options, amount); - }); + } } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Microsoft.Azure.Devices.Edge.Util.csproj b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Microsoft.Azure.Devices.Edge.Util.csproj index c4b8c006398..0bcaef17db9 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Microsoft.Azure.Devices.Edge.Util.csproj +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Microsoft.Azure.Devices.Edge.Util.csproj @@ -33,4 +33,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Option.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Option.cs index 50e641684d6..63a4ec50aeb 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Option.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Option.cs @@ -9,16 +9,22 @@ namespace Microsoft.Azure.Devices.Edge.Util public struct Option : IEquatable> { - public bool HasValue { get; } - - T Value { get; } - internal Option(T value, bool hasValue) { this.Value = value; this.HasValue = hasValue; } + public bool HasValue { get; } + + T Value { get; } + + [Pure] + public static bool operator ==(Option opt1, Option opt2) => opt1.Equals(opt2); + + [Pure] + public static bool operator !=(Option opt1, Option opt2) => !opt1.Equals(opt2); + [Pure] public bool Equals(Option other) { @@ -30,18 +36,13 @@ public bool Equals(Option other) { return EqualityComparer.Default.Equals(this.Value, other.Value); } + return false; } [Pure] public override bool Equals(object obj) => obj is Option && this.Equals((Option)obj); - [Pure] - public static bool operator ==(Option opt1, Option opt2) => opt1.Equals(opt2); - - [Pure] - public static bool operator !=(Option opt1, Option opt2) => !opt1.Equals(opt2); - [Pure] public override int GetHashCode() { @@ -49,6 +50,7 @@ public override int GetHashCode() { return this.Value == null ? 1 : this.Value.GetHashCode(); } + return 0; } @@ -81,6 +83,7 @@ public bool Contains(T value) { return this.Value == null ? value == null : this.Value.Equals(value); } + return false; } @@ -149,15 +152,13 @@ public Option Map(Func mapping) { return this.Match( some: value => Option.Some(mapping(value)), - none: Option.None - ); + none: Option.None); } [Pure] public Option FlatMap(Func> mapping) => this.Match( some: mapping, - none: Option.None - ); + none: Option.None); /// /// This method returns this if returns true and @@ -215,6 +216,7 @@ public static Option Some(T value) /// public static Option None() => new Option(default(T), false); - public static Option Maybe(T value) where T : class => value == null ? None() : Some(value); + public static Option Maybe(T value) + where T : class => value == null ? None() : Some(value); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Preconditions.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Preconditions.cs index 7aa43cbee32..09a81ec5d6b 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Preconditions.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Preconditions.cs @@ -11,7 +11,7 @@ public class Preconditions /// /// /// The reference - public static T CheckNotNull(T reference) => CheckNotNull(reference, "", ""); + public static T CheckNotNull(T reference) => CheckNotNull(reference, string.Empty, string.Empty); /// /// Checks that a reference isn't null. Throws ArgumentNullException if null. @@ -20,7 +20,7 @@ public class Preconditions /// /// /// The reference - public static T CheckNotNull(T reference, string paramName) => CheckNotNull(reference, paramName, ""); + public static T CheckNotNull(T reference, string paramName) => CheckNotNull(reference, paramName, string.Empty); /// /// Checks that a reference isn't null. Throws ArgumentNullException if null. @@ -43,6 +43,7 @@ public static T CheckNotNull(T reference, string paramName, string message) throw string.IsNullOrEmpty(message) ? new ArgumentNullException(paramName) : new ArgumentNullException(paramName, message); } } + return reference; } @@ -71,7 +72,6 @@ public static void CheckArgument(bool expression, string message) } } - /// /// Checks that an Enum is defined. Throws ArgumentOutOfRangeException is not. /// @@ -83,12 +83,12 @@ public static T CheckIsDefined(T status) Type enumType = typeof(T); if (!Enum.IsDefined(enumType, status)) { - throw new ArgumentOutOfRangeException(status + " is not a valid value for " + enumType.FullName + "."); + throw new ArgumentOutOfRangeException(status + " is not a valid value for " + enumType.FullName + "."); } + return status; } - /// /// This checks that the item is greater than or equal to the low value. /// @@ -96,7 +96,8 @@ public static T CheckIsDefined(T status) /// Item to check. /// Inclusive low value. /// - public static T CheckRange(T item, T low) where T : IComparable => + public static T CheckRange(T item, T low) + where T : IComparable => CheckRange(item, low, nameof(item)); /// @@ -107,8 +108,9 @@ public static T CheckRange(T item, T low) where T : IComparable => /// Inclusive low value. /// /// - public static T CheckRange(T item, T low, string paramName) where T : IComparable => - CheckRange(item, low, paramName, ""); + public static T CheckRange(T item, T low, string paramName) + where T : IComparable => + CheckRange(item, low, paramName, string.Empty); /// /// This checks that the item is greater than or equal to the low value. @@ -119,12 +121,14 @@ public static T CheckRange(T item, T low, string paramName) where T : ICompar /// /// /// - public static T CheckRange(T item, T low, string paramName, string message) where T : IComparable + public static T CheckRange(T item, T low, string paramName, string message) + where T : IComparable { if (item.CompareTo(low) < 0) { throw new ArgumentOutOfRangeException(paramName, item, message); } + return item; } @@ -137,7 +141,8 @@ public static T CheckRange(T item, T low, string paramName, string message) w /// Inclusive low value. /// Exclusive high value /// - public static T CheckRange(T item, T low, T high) where T : IComparable => + public static T CheckRange(T item, T low, T high) + where T : IComparable => CheckRange(item, low, high, nameof(item)); /// @@ -150,8 +155,9 @@ public static T CheckRange(T item, T low, T high) where T : IComparable => /// Exclusive high value /// /// - public static T CheckRange(T item, T low, T high, string paramName) where T : IComparable => - CheckRange(item, low, high, paramName, ""); + public static T CheckRange(T item, T low, T high, string paramName) + where T : IComparable => + CheckRange(item, low, high, paramName, string.Empty); /// /// This checks that the item is in the range [low, high). @@ -164,12 +170,14 @@ public static T CheckRange(T item, T low, T high, string paramName) where T : /// /// /// - public static T CheckRange(T item, T low, T high, string paramName, string message) where T : IComparable + public static T CheckRange(T item, T low, T high, string paramName, string message) + where T : IComparable { if (item.CompareTo(low) < 0 || item.CompareTo(high) >= 0) { throw new ArgumentOutOfRangeException(paramName, item, message); } + return item; } @@ -183,6 +191,5 @@ public static string CheckNonWhiteSpace(string value, string paramName) CheckArgument(!string.IsNullOrWhiteSpace(value), $"{paramName} is null or whitespace."); return value; } - } -} \ No newline at end of file +} diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ResettableTimer.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ResettableTimer.cs index 574a7b93804..96cf8d30393 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ResettableTimer.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ResettableTimer.cs @@ -69,6 +69,8 @@ public void Enable() } } + public void Dispose() => this.Disable(); + Timer CreateTimer() { var instance = new Timer(this.period.TotalMilliseconds); @@ -87,7 +89,5 @@ async void TimerOnElapsed(object sender, ElapsedEventArgs e) this.logger?.LogWarning($"Error in timer callback - {exception}"); } } - - public void Dispose() => this.Disable(); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Retry.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Retry.cs index 1250f647a22..f9962117b2c 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Retry.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Retry.cs @@ -7,29 +7,6 @@ namespace Microsoft.Azure.Devices.Edge.Util public static class Retry { - static async Task> DoOnce( - Func> func, - Func isValidValue, - Func continueOnException) - { - try - { - T result = await func(); - if (isValidValue == null || isValidValue(result)) - { - return Option.Some(result); - } - } - catch (Exception e) - { - if (continueOnException == null || !continueOnException(e)) - { - throw; - } - } - return Option.None(); - } - public static async Task Do( Func> func, Func isValidValue, @@ -51,7 +28,32 @@ public static async Task Do( await Task.Delay(retryInterval, token); } } + throw new TaskCanceledException(); } + + static async Task> DoOnce( + Func> func, + Func isValidValue, + Func continueOnException) + { + try + { + T result = await func(); + if (isValidValue == null || isValidValue(result)) + { + return Option.Some(result); + } + } + catch (Exception e) + { + if (continueOnException == null || !continueOnException(e)) + { + throw; + } + } + + return Option.None(); + } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/SasTokenHelper.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/SasTokenHelper.cs index 41ac7815160..39461061424 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/SasTokenHelper.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/SasTokenHelper.cs @@ -19,13 +19,17 @@ public static string BuildSasToken(string audience, string signature, string exp { // Example returned string: // SharedAccessSignature sr=ENCODED(dh://myiothub.azure-devices.net/a/b/c?myvalue1=a)&sig=&se=[&skn=] - var buffer = new StringBuilder(); - buffer.AppendFormat(CultureInfo.InvariantCulture, "{0} {1}={2}&{3}={4}&{5}={6}", + buffer.AppendFormat( + CultureInfo.InvariantCulture, + "{0} {1}={2}&{3}={4}&{5}={6}", SharedAccessSignature, - AudienceFieldName, audience, - SignatureFieldName, WebUtility.UrlEncode(signature), - ExpiryFieldName, WebUtility.UrlEncode(expiry)); + AudienceFieldName, + audience, + SignatureFieldName, + WebUtility.UrlEncode(signature), + ExpiryFieldName, + WebUtility.UrlEncode(expiry)); return buffer.ToString(); } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs index f4efe07e26a..53189d21cd5 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs @@ -29,6 +29,7 @@ public static (CancellationTokenSource cts, ManualResetEventSlim doneSignal, Opt { LinuxShutdownHandler.Init(cts, completed, shutdownWaitPeriod, logger); } + return (cts, completed, handler); } @@ -67,8 +68,6 @@ void CancelProgram() /// static class WindowsShutdownHandler { - [DllImport("Kernel32")] - static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); public delegate bool HandlerRoutine(CtrlTypes ctrlType); public enum CtrlTypes @@ -86,30 +85,34 @@ public static HandlerRoutine Init( TimeSpan waitPeriod, ILogger logger) { - var hr = new HandlerRoutine(type => - { - logger?.LogInformation($"Received signal of type {type}"); - if (type == CtrlTypes.CTRL_SHUTDOWN_EVENT) + var hr = new HandlerRoutine( + type => { - logger?.LogInformation("Initiating shutdown"); - cts.Cancel(); - logger?.LogInformation("Waiting for cleanup to finish"); - if (completed.Wait(waitPeriod)) - { - logger?.LogInformation("Done with cleanup. Shutting down."); - } - else + logger?.LogInformation($"Received signal of type {type}"); + if (type == CtrlTypes.CTRL_SHUTDOWN_EVENT) { - logger?.LogInformation("Timed out waiting for cleanup to finish. Shutting down."); + logger?.LogInformation("Initiating shutdown"); + cts.Cancel(); + logger?.LogInformation("Waiting for cleanup to finish"); + if (completed.Wait(waitPeriod)) + { + logger?.LogInformation("Done with cleanup. Shutting down."); + } + else + { + logger?.LogInformation("Timed out waiting for cleanup to finish. Shutting down."); + } } - } - return false; - }); + return false; + }); SetConsoleCtrlHandler(hr, true); logger?.LogDebug("Waiting on shutdown handler to trigger"); return hr; } + + [DllImport("Kernel32")] + static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/StringEx.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/StringEx.cs index 5f597f60c27..4c120203cb5 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/StringEx.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/StringEx.cs @@ -30,7 +30,7 @@ public static IEnumerable Chunks(this string self, int size) /// /// /// - public static string Join(this IEnumerable strings) => strings.Join(""); + public static string Join(this IEnumerable strings) => strings.Join(string.Empty); /// /// Adds an extension method to make string.Join more ergonomic diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/TaskEx.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/TaskEx.cs index e6e807d1474..54c8c353e20 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/TaskEx.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/TaskEx.cs @@ -176,31 +176,6 @@ public static Task TimeoutAfter(this Func> oper } } - static async Task ExecuteUntilCancelled(this Task task, CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - cancellationToken.Register( - () => - { - tcs.SetException(new TaskCanceledException(task)); - }); - Task completedTask = await Task.WhenAny(task, tcs.Task); - return await completedTask; - } - - static async Task ExecuteUntilCancelled(this Task task, CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - cancellationToken.Register( - () => - { - tcs.TrySetCanceled(); - }); - Task completedTask = await Task.WhenAny(task, tcs.Task); - //// Await here to bubble up any exceptions - await completedTask; - } - public static Task ExecuteUntilCancelled(this Func operation, CancellationToken cancellationToken) { Preconditions.CheckNotNull(operation, nameof(operation)); @@ -214,7 +189,7 @@ public static Task ExecuteUntilCancelled(this Action operation, CancellationToke Task task = Task.Run(operation, cancellationToken); return task.ExecuteUntilCancelled(cancellationToken); } - + public static IAsyncResult ToAsyncResult(this Task task, AsyncCallback callback, object state) { if (task.AsyncState == state) @@ -273,5 +248,24 @@ public static void EndAsyncResult(IAsyncResult asyncResult) throw ae.GetBaseException(); } } + + static async Task ExecuteUntilCancelled(this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + cancellationToken.Register( + () => { tcs.SetException(new TaskCanceledException(task)); }); + Task completedTask = await Task.WhenAny(task, tcs.Task); + return await completedTask; + } + + static async Task ExecuteUntilCancelled(this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + cancellationToken.Register( + () => { tcs.TrySetCanceled(); }); + Task completedTask = await Task.WhenAny(task, tcs.Task); + //// Await here to bubble up any exceptions + await completedTask; + } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Try.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Try.cs index 7ffaf49f705..030f8df1bc7 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Try.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/Try.cs @@ -15,7 +15,7 @@ public Try(T value) } public Try(Exception exception) - { + { this.value = default(T); this.Exception = Preconditions.CheckNotNull(exception); } @@ -28,6 +28,7 @@ public T Value { ExceptionDispatchInfo.Capture(this.Exception).Throw(); } + return this.value; } } @@ -36,11 +37,11 @@ public T Value public bool Success => this.Exception == null; - public Option Ok() => this.Success ? Option.Some(this.Value) : Option.None(); - public static Try Failure(Exception exception) => new Try(exception); public static implicit operator Try(T value) => new Try(value); + + public Option Ok() => this.Success ? Option.Some(this.Value) : Option.None(); } public static class Try diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/UtilEventsIds.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/UtilEventsIds.cs index 76f352e2fae..c75a270ac81 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/UtilEventsIds.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/UtilEventsIds.cs @@ -3,8 +3,8 @@ namespace Microsoft.Azure.Devices.Edge.Util { public struct UtilEventsIds { - const int EventIdStart = 100000; public const int HttpUdsMessageHandler = EventIdStart; public const int EdgeletWorkloadClient = EventIdStart + 100; + const int EventIdStart = 100000; } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/VersionInfo.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/VersionInfo.cs index 72e08147a6c..98df2f93e9f 100755 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/VersionInfo.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/VersionInfo.cs @@ -19,6 +19,15 @@ public VersionInfo(string version, string build, string commit) public static VersionInfo Empty { get; } = new VersionInfo(string.Empty, string.Empty, string.Empty); + [JsonProperty(PropertyName = "version")] + public string Version { get; } + + [JsonProperty(PropertyName = "build")] + public string Build { get; } + + [JsonProperty(PropertyName = "commit")] + public string Commit { get; } + public static VersionInfo Get(string fileName) { try @@ -30,18 +39,16 @@ public static VersionInfo Get(string fileName) return JsonConvert.DeserializeObject(fileText); } } - catch (Exception ex) when (!ex.IsFatal()) { } + catch (Exception ex) when (!ex.IsFatal()) + { + } + return Empty; } - [JsonProperty(PropertyName = "version")] - public string Version { get; } - - [JsonProperty(PropertyName = "build")] - public string Build { get; } + public static bool operator ==(VersionInfo info1, VersionInfo info2) => EqualityComparer.Default.Equals(info1, info2); - [JsonProperty(PropertyName = "commit")] - public string Commit { get; } + public static bool operator !=(VersionInfo info1, VersionInfo info2) => !(info1 == info2); public string ToString(bool includeCommitId) { @@ -66,7 +73,7 @@ public string ToString(bool includeCommitId) return sb.ToString(); } - public override string ToString() => this.ToString(false); + public override string ToString() => this.ToString(false); public override bool Equals(object obj) => this.Equals(obj as VersionInfo); @@ -87,9 +94,5 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Commit); return hashCode; } - - public static bool operator ==(VersionInfo info1, VersionInfo info2) => EqualityComparer.Default.Equals(info1, info2); - - public static bool operator !=(VersionInfo info1, VersionInfo info2) => !(info1 == info2); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AsyncLock.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AsyncLock.cs index 59304b63ba1..2dfc7ec6163 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AsyncLock.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AsyncLock.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Concurrency { - // // Code ported from http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx - // using System; using System.Threading; using System.Threading.Tasks; @@ -29,10 +27,14 @@ public AsyncLock(int maximumConcurrency) public Task LockAsync(CancellationToken token) { Task wait = this.semaphore.WaitAsync(token); - return wait.Status == TaskStatus.RanToCompletion ? this.releaser : - wait.ContinueWith((_, state) => new Releaser((AsyncLock)state), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + return wait.Status == TaskStatus.RanToCompletion + ? this.releaser + : wait.ContinueWith( + (_, state) => new Releaser((AsyncLock)state), + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, + TaskScheduler.Default); } /// diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicBoolean.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicBoolean.cs index 3951e7b1058..ac3067a8766 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicBoolean.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicBoolean.cs @@ -12,7 +12,12 @@ public AtomicBoolean(bool value) this.underlying = value ? 1 : 0; } - public AtomicBoolean() : this(false) { } + public AtomicBoolean() + : this(false) + { + } + + public static implicit operator bool(AtomicBoolean value) => value.Get(); public bool Get() => Interlocked.Exchange(ref this.underlying, this.underlying) != 0; @@ -26,7 +31,5 @@ public bool CompareAndSet(bool expected, bool result) int r = result ? 1 : 0; return Interlocked.CompareExchange(ref this.underlying, r, e) == e; } - - public static implicit operator bool(AtomicBoolean value) => value.Get(); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicLong.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicLong.cs index 15a36505b02..07f94bde9d6 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicLong.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/concurrency/AtomicLong.cs @@ -12,7 +12,12 @@ public AtomicLong(long value) this.underlying = value; } - public AtomicLong() : this(0) { } + public AtomicLong() + : this(0) + { + } + + public static implicit operator long(AtomicLong value) => value.Get(); public long Get() => this.underlying; @@ -26,7 +31,5 @@ public long CompareAndSet(long expected, long result) { return Interlocked.CompareExchange(ref this.underlying, result, expected); } - - public static implicit operator long(AtomicLong value) => value.Get(); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/EncryptionProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/EncryptionProvider.cs index a78093bdb55..c4404f7d821 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/EncryptionProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/EncryptionProvider.cs @@ -4,14 +4,13 @@ namespace Microsoft.Azure.Devices.Edge.Util.Edged using System; using System.IO; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Concurrency; public class EncryptionProvider : IEncryptionProvider { + static readonly AsyncLock asyncLock = new AsyncLock(); readonly string initializationVector; readonly WorkloadClient workloadClient; - static readonly AsyncLock asyncLock = new AsyncLock(); EncryptionProvider(Uri workloadUri, string apiVersion, string moduleId, string moduleGenerationid, string initializationVector) { @@ -19,10 +18,6 @@ public class EncryptionProvider : IEncryptionProvider this.workloadClient = new WorkloadClient(workloadUri, apiVersion, moduleId, moduleGenerationid); } - public Task DecryptAsync(string encryptedText) => this.workloadClient.DecryptAsync(this.initializationVector, encryptedText); - - public Task EncryptAsync(string plainText) => this.workloadClient.EncryptAsync(this.initializationVector, plainText); - public static async Task CreateAsync(string storagePath, Uri workloadUri, string edgeletWorkloadApiVersion, string moduleId, string genId, string initializationVectorFileName) { string ivFile = $"{storagePath}/{initializationVectorFileName}"; @@ -42,5 +37,9 @@ public static async Task CreateAsync(string storagePath, Uri return new EncryptionProvider(workloadUri, edgeletWorkloadApiVersion, moduleId, genId, iv); } + + public Task DecryptAsync(string encryptedText) => this.workloadClient.DecryptAsync(this.initializationVector, encryptedText); + + public Task EncryptAsync(string plainText) => this.workloadClient.EncryptAsync(this.initializationVector, plainText); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmCommunicationException.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmCommunicationException.cs index 51e77b06227..779682b99b5 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmCommunicationException.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmCommunicationException.cs @@ -8,19 +8,20 @@ namespace Microsoft.Azure.Devices.Edge.Util.Edged /// public class HttpHsmCommunicationException : Exception { - /// - /// Status code of the communication failure. - /// - public int StatusCode { get; } - /// /// Initializes a new instance of the class with the message string. /// /// A description of the error. The content of message is intended to be understood by humans. /// Status code of the communication failure. - public HttpHsmCommunicationException(string message, int statusCode) : base($"{message}, StatusCode: {statusCode}") + public HttpHsmCommunicationException(string message, int statusCode) + : base($"{message}, StatusCode: {statusCode}") { this.StatusCode = statusCode; } + + /// + /// Status code of the communication failure. + /// + public int StatusCode { get; } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmSignatureProvider.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmSignatureProvider.cs index 062b6f6b810..c856621c057 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmSignatureProvider.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/HttpHsmSignatureProvider.cs @@ -7,10 +7,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Edged using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Edged.GeneratedCode; using Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling; - using ErrorResponse = Microsoft.Azure.Devices.Edge.Util.Edged.GeneratedCode.ErrorResponse; - using SignRequest = Microsoft.Azure.Devices.Edge.Util.Edged.GeneratedCode.SignRequest; - using SignRequestAlgo = Microsoft.Azure.Devices.Edge.Util.Edged.GeneratedCode.SignRequestAlgo; - using SignResponse = Microsoft.Azure.Devices.Edge.Util.Edged.GeneratedCode.SignResponse; public class HttpHsmSignatureProvider : ISignatureProvider { @@ -22,11 +18,12 @@ public class HttpHsmSignatureProvider : ISignatureProvider readonly string generationId; static readonly ITransientErrorDetectionStrategy TransientErrorDetectionStrategy = new ErrorDetectionStrategy(); + static readonly RetryStrategy TransientRetryStrategy = new ExponentialBackoff(retryCount: 3, minBackoff: TimeSpan.FromSeconds(2), maxBackoff: TimeSpan.FromSeconds(30), deltaBackoff: TimeSpan.FromSeconds(3)); public HttpHsmSignatureProvider(string moduleId, string generationId, string providerUri, string apiVersion) - { + { this.providerUri = new Uri(Preconditions.CheckNotNull(providerUri, nameof(providerUri))); this.apiVersion = Preconditions.CheckNonWhiteSpace(apiVersion, nameof(apiVersion)); this.moduleId = Preconditions.CheckNonWhiteSpace(moduleId, nameof(moduleId)); diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/WorkloadClient.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/WorkloadClient.cs index 0f1e3b2cd39..25674452d6a 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/WorkloadClient.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/edged/WorkloadClient.cs @@ -12,8 +12,10 @@ namespace Microsoft.Azure.Devices.Edge.Util.Edged public class WorkloadClient { static readonly ITransientErrorDetectionStrategy TransientErrorDetectionStrategy = new ErrorDetectionStrategy(); + static readonly RetryStrategy TransientRetryStrategy = new ExponentialBackoff(retryCount: 3, minBackoff: TimeSpan.FromSeconds(2), maxBackoff: TimeSpan.FromSeconds(30), deltaBackoff: TimeSpan.FromSeconds(3)); + readonly Uri workloadUri; readonly string apiVersion; readonly string moduleId; @@ -83,6 +85,13 @@ public async Task DecryptAsync(string initializationVector, string encry } } + static Task ExecuteWithRetry(Func> func, Action onRetry) + { + var transientRetryPolicy = new RetryPolicy(TransientErrorDetectionStrategy, TransientRetryStrategy); + transientRetryPolicy.Retrying += (_, args) => onRetry(args); + return transientRetryPolicy.ExecuteAsync(func); + } + async Task Execute(Func> func, string operation) { try @@ -109,29 +118,23 @@ async Task Execute(Func> func, string operation) { throw new WorkloadCommunicationException($"Error calling {operation}: {swaggerException.Response ?? string.Empty}", swaggerException.StatusCode); } + default: throw; } } } - static Task ExecuteWithRetry(Func> func, Action onRetry) - { - var transientRetryPolicy = new RetryPolicy(TransientErrorDetectionStrategy, TransientRetryStrategy); - transientRetryPolicy.Retrying += (_, args) => onRetry(args); - return transientRetryPolicy.ExecuteAsync(func); - } - class ErrorDetectionStrategy : ITransientErrorDetectionStrategy { public bool IsTransient(Exception ex) => ex is IoTEdgedException se - && se.StatusCode >= 500; + && se.StatusCode >= 500; } static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = UtilEventsIds.EdgeletWorkloadClient; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/json/OptionConverter.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/json/OptionConverter.cs index 501c1c8a6fd..5a667fb365c 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/json/OptionConverter.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/json/OptionConverter.cs @@ -3,8 +3,11 @@ namespace Microsoft.Azure.Devices.Edge.Util.Json { using System; using Newtonsoft.Json; + public class OptionConverter : JsonConverter { + public override bool CanRead => false; + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value is Option option) @@ -19,8 +22,6 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotSupportedException(); - public override bool CanRead => false; - public override bool CanConvert(Type type) => type.IsGenericType && typeof(Option) == type.GetGenericTypeDefinition(); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/logging/LoggerEventListener.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/logging/LoggerEventListener.cs index 89a7c5196e3..d4ecb0cc5e5 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/logging/LoggerEventListener.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/logging/LoggerEventListener.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Logging { using System.Diagnostics.Tracing; using System.Linq; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Extensions.Logging; /// @@ -24,7 +23,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) } static string Formatter(EventWrittenEventArgs args) => - args?.Payload != null + args?.Payload != null ? string.Join(", ", args.Payload.Select(e => e.ToString())) : ""; @@ -47,4 +46,4 @@ static LogLevel GetLogLevel(EventLevel level) } } } -} \ No newline at end of file +} diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution.cs index 3a4e8c3821a..d2b5c50e150 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + /// /// Provides a wrapper for a non-generic and calls into the pipeline /// to retry only the generic version of the . @@ -29,38 +29,58 @@ static Task StartAsGenericTask(Func taskAction) Task task = taskAction(); if (task == null) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} cannot be null", new object[] - { - "taskAction" - }), nameof(taskAction)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} cannot be null", + new object[] + { + "taskAction" + }), + nameof(taskAction)); } + if (task.Status == TaskStatus.RanToCompletion) { return GetCachedTask(); } + if (task.Status == TaskStatus.Created) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} must be scheduled", new object[] - { - "taskAction" - }), nameof(taskAction)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} must be scheduled", + new object[] + { + "taskAction" + }), + nameof(taskAction)); } + var tcs = new TaskCompletionSource(); - task.ContinueWith(delegate (Task t) - { - if (t.IsFaulted) - { - if (t.Exception != null) - tcs.TrySetException(t.Exception.InnerExceptions); - return; - } - if (t.IsCanceled) + task.ContinueWith( + t => { - tcs.TrySetCanceled(); - return; - } - tcs.TrySetResult(true); - }, TaskContinuationOptions.ExecuteSynchronously); + if (t.IsFaulted) + { + if (t.Exception != null) + { + tcs.TrySetException(t.Exception.InnerExceptions); + } + + return; + } + + if (t.IsCanceled) + { + tcs.TrySetCanceled(); + return; + } + + tcs.TrySetResult(true); + }, + TaskContinuationOptions.ExecuteSynchronously); return tcs.Task; } @@ -72,6 +92,7 @@ static Task GetCachedTask() taskCompletionSource.TrySetResult(true); cachedBoolTask = taskCompletionSource.Task; } + return cachedBoolTask; } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution[T].cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution[T].cs index 8a3eacc6058..1be8661bd3a 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution[T].cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/AsyncExecution[T].cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + using System.Globalization; + using System.Threading; + using System.Threading.Tasks; + /// /// Handles the execution and retries of the user-initiated task. /// @@ -51,6 +51,7 @@ Task ExecuteAsyncImpl(Task ignore) { return this.previousTask; } + var taskCompletionSource = new TaskCompletionSource(); taskCompletionSource.TrySetCanceled(); return taskCompletionSource.Task; @@ -68,28 +69,43 @@ Task ExecuteAsyncImpl(Task ignore) { throw; } + var taskCompletionSource2 = new TaskCompletionSource(); taskCompletionSource2.TrySetException(ex); task = taskCompletionSource2.Task; } + if (task == null) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} cannot be null", new object[] - { - "taskFunc" - }), nameof(this.taskFunc)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} cannot be null", + new object[] + { + "taskFunc" + }), + nameof(this.taskFunc)); } + if (task.Status == TaskStatus.RanToCompletion) { return task; } + if (task.Status == TaskStatus.Created) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} must be scheduled", new object[] - { - "taskFunc" - }), nameof(this.taskFunc)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "{0} must be scheduled", + new object[] + { + "taskFunc" + }), + nameof(this.taskFunc)); } + return task.ContinueWith(new Func, Task>(this.ExecuteAsyncContinueWith), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap(); } } @@ -100,6 +116,7 @@ Task ExecuteAsyncContinueWith(Task runningTask) { return runningTask; } + TimeSpan zero; Exception innerException = runningTask.Exception.InnerException; #pragma warning disable CS0618 // Type or member is obsolete @@ -115,22 +132,27 @@ Task ExecuteAsyncContinueWith(Task runningTask) { taskCompletionSource.TrySetCanceled(); } + return taskCompletionSource.Task; } + if (!this.isTransient(innerException) || !this.shouldRetry(this.retryCount++, innerException, out zero)) { return runningTask; } + if (zero < TimeSpan.Zero) { zero = TimeSpan.Zero; } + this.onRetrying(this.retryCount, innerException, zero); this.previousTask = runningTask; if (zero > TimeSpan.Zero && (this.retryCount > 1 || !this.fastFirstRetry)) { return Task.Delay(zero, this.cancellationToken).ContinueWith(new Func>(this.ExecuteAsyncImpl), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default).Unwrap(); } + return this.ExecuteAsyncImpl(null); } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ExponentialBackoff.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ExponentialBackoff.cs index 75b5a87913b..3c81db975c9 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ExponentialBackoff.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ExponentialBackoff.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// A retry strategy with back-off parameters for calculating the exponential delay between retries. /// Note: this fixes an overflow in the stock ExponentialBackoff in the Transient Fault Handling library diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/FixedInterval.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/FixedInterval.cs index ea36eb359c2..72c309e1847 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/FixedInterval.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/FixedInterval.cs @@ -1,48 +1,52 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// Represents a retry strategy with a specified number of retry attempts and a default, fixed time interval between retries. /// public class FixedInterval : RetryStrategy { - private readonly int retryCount; + readonly int retryCount; - private readonly TimeSpan retryInterval; + readonly TimeSpan retryInterval; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public FixedInterval() : this(DefaultClientRetryCount) + public FixedInterval() + : this(DefaultClientRetryCount) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts. + /// Initializes a new instance of the class with the specified number of retry attempts. /// /// The number of retry attempts. - public FixedInterval(int retryCount) : this(retryCount, DefaultRetryInterval) + public FixedInterval(int retryCount) + : this(retryCount, DefaultRetryInterval) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts, time interval, and retry strategy. + /// Initializes a new instance of the class with the specified number of retry attempts, time interval, and retry strategy. /// /// The number of retry attempts. /// The time interval between retries. - public FixedInterval(int retryCount, TimeSpan retryInterval) : this(retryCount, retryInterval, DefaultFirstFastRetry) + public FixedInterval(int retryCount, TimeSpan retryInterval) + : this(retryCount, retryInterval, DefaultFirstFastRetry) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. + /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. /// /// The number of retry attempts. /// The time interval between retries. /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. - public FixedInterval(int retryCount, TimeSpan retryInterval, bool firstFastRetry) : base(firstFastRetry) + public FixedInterval(int retryCount, TimeSpan retryInterval, bool firstFastRetry) + : base(firstFastRetry) { Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); Guard.ArgumentNotNegativeValue(retryInterval.Ticks, "retryInterval"); @@ -58,19 +62,21 @@ public override ShouldRetry GetShouldRetry() { if (this.retryCount == 0) { - return delegate (int currentRetryCount, Exception lastException, out TimeSpan interval) + return (int currentRetryCount, Exception lastException, out TimeSpan interval) => { interval = TimeSpan.Zero; return false; }; } - return delegate (int currentRetryCount, Exception lastException, out TimeSpan interval) + + return (int currentRetryCount, Exception lastException, out TimeSpan interval) => { if (currentRetryCount < this.retryCount) { interval = this.retryInterval; return true; } + interval = TimeSpan.Zero; return false; }; diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Guard.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Guard.cs index 7cd0d237abf..1c184cf308b 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Guard.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Guard.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Globalization; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + using System.Globalization; + /// /// Implements the common guard methods. /// - internal static class Guard + static class Guard { /// /// Checks a string argument to ensure that it isn't null or empty. @@ -20,11 +20,16 @@ public static bool ArgumentNotNullOrEmptyString(string argumentValue, string arg ArgumentNotNull(argumentValue, argumentName); if (argumentValue.Length == 0) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "String {0} cannot be empty", new object[] - { - argumentName - })); + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + "String {0} cannot be empty", + new object[] + { + argumentName + })); } + return true; } @@ -40,6 +45,7 @@ public static bool ArgumentNotNull(object argumentValue, string argumentName) { throw new ArgumentNullException(argumentName); } + return true; } @@ -52,10 +58,16 @@ public static void ArgumentNotNegativeValue(int argumentValue, string argumentNa { if (argumentValue < 0) { - throw new ArgumentOutOfRangeException(argumentName, argumentValue, string.Format(CultureInfo.CurrentCulture, "Argument {0} cannot be negative", new object[] - { - argumentName - })); + throw new ArgumentOutOfRangeException( + argumentName, + argumentValue, + string.Format( + CultureInfo.CurrentCulture, + "Argument {0} cannot be negative", + new object[] + { + argumentName + })); } } @@ -68,10 +80,16 @@ public static void ArgumentNotNegativeValue(long argumentValue, string argumentN { if (argumentValue < 0L) { - throw new ArgumentOutOfRangeException(argumentName, argumentValue, string.Format(CultureInfo.CurrentCulture, "Argument {0} cannot be negative", new object[] - { - argumentName - })); + throw new ArgumentOutOfRangeException( + argumentName, + argumentValue, + string.Format( + CultureInfo.CurrentCulture, + "Argument {0} cannot be negative", + new object[] + { + argumentName + })); } } @@ -85,11 +103,17 @@ public static void ArgumentNotGreaterThan(double argumentValue, double ceilingVa { if (argumentValue > ceilingValue) { - throw new ArgumentOutOfRangeException(argumentName, argumentValue, string.Format(CultureInfo.CurrentCulture, "Argument {0} cannot be greater than baseline value {1}", new object[] - { + throw new ArgumentOutOfRangeException( argumentName, - ceilingValue - })); + argumentValue, + string.Format( + CultureInfo.CurrentCulture, + "Argument {0} cannot be greater than baseline value {1}", + new object[] + { + argumentName, + ceilingValue + })); } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ITransientErrorDetectionStrategy.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ITransientErrorDetectionStrategy.cs index 003c328dc25..2fd7ee515a0 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ITransientErrorDetectionStrategy.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ITransientErrorDetectionStrategy.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// Defines an interface that must be implemented by custom components responsible for detecting specific transient conditions. /// diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Incremental.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Incremental.cs index 80fd7786ab3..53b9f9107ad 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Incremental.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/Incremental.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// A retry strategy with a specified number of retry attempts and an incremental time interval between retries. /// @@ -13,9 +13,10 @@ public class Incremental : RetryStrategy readonly TimeSpan increment; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Incremental() : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) + public Incremental() + : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) { } @@ -25,18 +26,20 @@ public Incremental() : this(DefaultClientRetryCount, DefaultRetryInterval, Defau /// The number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive delay between retries. - public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment) : this(retryCount, initialInterval, increment, DefaultFirstFastRetry) + public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(retryCount, initialInterval, increment, DefaultFirstFastRetry) { } /// - /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. + /// Initializes a new instance of the class with the specified number of retry attempts, time interval, retry strategy, and fast start option. /// /// The number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive 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 Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) : base(firstFastRetry) + public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) + : base(firstFastRetry) { Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); Guard.ArgumentNotNegativeValue(initialInterval.Ticks, "initialInterval"); @@ -52,13 +55,14 @@ public Incremental(int retryCount, TimeSpan initialInterval, TimeSpan increment, /// The ShouldRetry delegate. public override ShouldRetry GetShouldRetry() { - return delegate (int currentRetryCount, Exception lastException, out TimeSpan retryInterval) + return (int currentRetryCount, Exception lastException, out TimeSpan retryInterval) => { if (currentRetryCount < this.retryCount) { retryInterval = TimeSpan.FromMilliseconds(this.initialInterval.TotalMilliseconds + this.increment.TotalMilliseconds * currentRetryCount); return true; } + retryInterval = TimeSpan.Zero; return false; }; diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryLimitExceededException.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryLimitExceededException.cs index 7f89f6b155d..6f40def84ba 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryLimitExceededException.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryLimitExceededException.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// The special type of exception that provides managed exit from a retry loop. The user code can use this /// exception to notify the retry policy that no further retry attempts are required. @@ -13,7 +13,8 @@ public sealed class RetryLimitExceededException : Exception /// /// Initializes a new instance of the class with a default error message. /// - public RetryLimitExceededException() : this("Retry limit exceeded") + public RetryLimitExceededException() + : this("Retry limit exceeded") { } @@ -21,7 +22,8 @@ public RetryLimitExceededException() : this("Retry limit exceeded") /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. - public RetryLimitExceededException(string message) : base(message) + public RetryLimitExceededException(string message) + : base(message) { } @@ -30,7 +32,8 @@ public RetryLimitExceededException(string message) : base(message) /// that is the cause of this exception. /// /// The exception that is the cause of the current exception. - public RetryLimitExceededException(Exception innerException) : base((innerException != null) ? innerException.Message : "Retry limit exceeded", innerException) + public RetryLimitExceededException(Exception innerException) + : base((innerException != null) ? innerException.Message : "Retry limit exceeded", innerException) { } @@ -39,7 +42,8 @@ public RetryLimitExceededException(Exception innerException) : base((innerExcept /// /// The message that describes the error. /// The exception that is the cause of the current exception. - public RetryLimitExceededException(string message, Exception innerException) : base(message, innerException) + public RetryLimitExceededException(string message, Exception innerException) + : base(message, innerException) { } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryPolicy.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryPolicy.cs index 18144bbc698..505817dc912 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryPolicy.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryPolicy.cs @@ -1,91 +1,15 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + using System.Threading; + using System.Threading.Tasks; + /// /// Provides the base implementation of the retry mechanism for unreliable actions and transient conditions. /// public class RetryPolicy { - /// - /// Implements a strategy that ignores any transient errors. - /// - sealed class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy - { - /// - /// Always returns false. - /// - /// The exception. - /// Always false. - public bool IsTransient(Exception ex) - { - return false; - } - } - - /// - /// Implements a strategy that treats all exceptions as transient errors. - /// - sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy - { - /// - /// Always returns true. - /// - /// The exception. - /// Always true. - public bool IsTransient(Exception ex) - { - return true; - } - } - - /// - /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. - /// - public event EventHandler Retrying; - - /// - /// Returns a default policy that performs no retries, but invokes the action only once. - /// - public static RetryPolicy NoRetry { get; } = new RetryPolicy(new RetryPolicy.TransientErrorIgnoreStrategy(), RetryStrategy.NoRetry); - - /// - /// Returns a default policy that implements a fixed retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static RetryPolicy DefaultFixed { get; } = new RetryPolicy(new RetryPolicy.TransientErrorCatchAllStrategy(), RetryStrategy.DefaultFixed); - - /// - /// Returns a default policy that implements a progressive retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static RetryPolicy DefaultProgressive { get; } = new RetryPolicy(new RetryPolicy.TransientErrorCatchAllStrategy(), RetryStrategy.DefaultProgressive); - - /// - /// Returns a default policy that implements a random exponential retry interval configured with the default retry strategy. - /// The default retry policy treats all caught exceptions as transient errors. - /// - public static RetryPolicy DefaultExponential { get; } = new RetryPolicy(new RetryPolicy.TransientErrorCatchAllStrategy(), RetryStrategy.DefaultExponential); - - /// - /// Gets the retry strategy. - /// - public RetryStrategy RetryStrategy - { - get; - } - - /// - /// Gets the instance of the error detection strategy. - /// - public ITransientErrorDetectionStrategy ErrorDetectionStrategy - { - get; - } - /// /// Initializes a new instance of the class with the specified number of retry attempts and parameters defining the progressive delay between retries. /// @@ -100,6 +24,7 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr { throw new InvalidOperationException("The error detection strategy type must implement the ITransientErrorDetectionStrategy interface."); } + this.RetryStrategy = retryStrategy; } @@ -108,7 +33,8 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// /// The that is responsible for detecting transient conditions. /// The number of retry attempts. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) : this(errorDetectionStrategy, new FixedInterval(retryCount)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) + : this(errorDetectionStrategy, new FixedInterval(retryCount)) { } @@ -118,7 +44,8 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// The that is responsible for detecting transient conditions. /// The number of retry attempts. /// The interval between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) : this(errorDetectionStrategy, new FixedInterval(retryCount, retryInterval)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) + : this(errorDetectionStrategy, new FixedInterval(retryCount, retryInterval)) { } @@ -130,7 +57,8 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// The minimum backoff time. /// The maximum backoff time. /// The time value that will be used to calculate a random delta in the exponential delay between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(errorDetectionStrategy, new ExponentialBackoff(retryCount, minBackoff, maxBackoff, deltaBackoff)) { } @@ -141,10 +69,49 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr /// The number of retry attempts. /// The initial interval that will apply for the first retry. /// The incremental time value that will be used to calculate the progressive delay between retries. - public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, TimeSpan increment) : this(errorDetectionStrategy, new Incremental(retryCount, initialInterval, increment)) + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(errorDetectionStrategy, new Incremental(retryCount, initialInterval, increment)) { } + /// + /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. + /// + public event EventHandler Retrying; + + /// + /// Returns a default policy that performs no retries, but invokes the action only once. + /// + public static RetryPolicy NoRetry { get; } = new RetryPolicy(new TransientErrorIgnoreStrategy(), RetryStrategy.NoRetry); + + /// + /// Returns a default policy that implements a fixed retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static RetryPolicy DefaultFixed { get; } = new RetryPolicy(new TransientErrorCatchAllStrategy(), RetryStrategy.DefaultFixed); + + /// + /// Returns a default policy that implements a progressive retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static RetryPolicy DefaultProgressive { get; } = new RetryPolicy(new TransientErrorCatchAllStrategy(), RetryStrategy.DefaultProgressive); + + /// + /// Returns a default policy that implements a random exponential retry interval configured with the default retry strategy. + /// The default retry policy treats all caught exceptions as transient errors. + /// + public static RetryPolicy DefaultExponential { get; } = new RetryPolicy(new TransientErrorCatchAllStrategy(), RetryStrategy.DefaultExponential); + + /// + /// Gets the retry strategy. + /// + public RetryStrategy RetryStrategy { get; } + + /// + /// Gets the instance of the error detection strategy. + /// + public ITransientErrorDetectionStrategy ErrorDetectionStrategy { get; } + /// /// Repetitively executes the specified action while it satisfies the current retry policy. /// @@ -152,11 +119,12 @@ public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, Retr public virtual void ExecuteAction(Action action) { Guard.ArgumentNotNull(action, "action"); - this.ExecuteAction(delegate - { - action(); - return null; - }); + this.ExecuteAction( + delegate + { + action(); + return null; + }); } /// @@ -188,6 +156,7 @@ public virtual TResult ExecuteAction(Func func) { throw ex2.InnerException; } + result = default(TResult); break; } @@ -199,16 +168,19 @@ public virtual TResult ExecuteAction(Func func) throw; } } + if (zero.TotalMilliseconds < 0.0) { zero = TimeSpan.Zero; } + this.OnRetrying(num, ex, zero); if (num > 1 || !this.RetryStrategy.FastFirstRetry) { Task.Delay(zero).Wait(); } } + return result; } @@ -242,6 +214,7 @@ public Task ExecuteAsync(Func taskAction, CancellationToken cancellationTo { throw new ArgumentNullException(nameof(taskAction)); } + return new AsyncExecution(taskAction, this.RetryStrategy.GetShouldRetry(), new Func(this.ErrorDetectionStrategy.IsTransient), new Action(this.OnRetrying), this.RetryStrategy.FastFirstRetry, cancellationToken).ExecuteAsync(); } @@ -275,6 +248,7 @@ public Task ExecuteAsync(Func> taskFunc, Cancell { throw new ArgumentNullException(nameof(taskFunc)); } + return new AsyncExecution(taskFunc, this.RetryStrategy.GetShouldRetry(), new Func(this.ErrorDetectionStrategy.IsTransient), new Action(this.OnRetrying), this.RetryStrategy.FastFirstRetry, cancellationToken).ExecuteAsync(); } @@ -288,5 +262,37 @@ protected virtual void OnRetrying(int retryCount, Exception lastError, TimeSpan { this.Retrying?.Invoke(this, new RetryingEventArgs(retryCount, lastError)); } + + /// + /// Implements a strategy that treats all exceptions as transient errors. + /// + sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always returns true. + /// + /// The exception. + /// Always true. + public bool IsTransient(Exception ex) + { + return true; + } + } + + /// + /// Implements a strategy that ignores any transient errors. + /// + sealed class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always returns false. + /// + /// The exception. + /// Always false. + public bool IsTransient(Exception ex) + { + return false; + } + } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryStrategy.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryStrategy.cs index 86c39734ce4..8d765a13f41 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryStrategy.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryStrategy.cs @@ -44,6 +44,15 @@ public abstract class RetryStrategy /// public static readonly bool DefaultFirstFastRetry = true; + /// + /// Initializes a new instance of the class. + /// + /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. + protected RetryStrategy(bool firstFastRetry) + { + this.FastFirstRetry = firstFastRetry; + } + /// /// Returns a default policy that performs no retries, but invokes the action only once. /// @@ -71,20 +80,7 @@ public abstract class RetryStrategy /// 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; - } - - /// - /// Initializes a new instance of the class. - /// - /// true to immediately retry in the first attempt; otherwise, false. The subsequent retries will remain subject to the configured retry interval. - protected RetryStrategy(bool firstFastRetry) - { - this.FastFirstRetry = firstFastRetry; - } + public bool FastFirstRetry { get; set; } /// /// Returns the corresponding ShouldRetry delegate. diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryingEventArgs.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryingEventArgs.cs index dd0158e92d1..37e82523896 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryingEventArgs.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/RetryingEventArgs.cs @@ -1,31 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// Contains information that is required for the event. /// public class RetryingEventArgs : EventArgs { - /// - /// Gets the current retry count. - /// - public int CurrentRetryCount - { - get; - set; - } - - /// - /// Gets the exception that caused the retry conditions to occur. - /// - public Exception LastException - { - get; - set; - } - /// /// Initializes a new instance of the class. /// @@ -37,5 +19,15 @@ public RetryingEventArgs(int currentRetryCount, Exception lastException) this.CurrentRetryCount = currentRetryCount; this.LastException = lastException; } + + /// + /// Gets the current retry count. + /// + public int CurrentRetryCount { get; set; } + + /// + /// Gets the exception that caused the retry conditions to occur. + /// + public Exception LastException { get; set; } } } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ShouldRetry.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ShouldRetry.cs index d1b7570d6cd..1fedb418410 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ShouldRetry.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/transientFaultHandling/ShouldRetry.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; - namespace Microsoft.Azure.Devices.Edge.Util.TransientFaultHandling { + using System; + /// /// Defines a callback delegate that will be invoked whenever a retry condition is encountered. /// diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpBufferedStream.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpBufferedStream.cs index 57f70b68546..91e43c24cba 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpBufferedStream.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpBufferedStream.cs @@ -5,18 +5,32 @@ namespace Microsoft.Azure.Devices.Edge.Util.Uds using System.Text; using System.Threading; using System.Threading.Tasks; - + class HttpBufferedStream : Stream { - private const char CR = '\r'; - private const char LF = '\n'; - private BufferedStream innerStream; + const char CR = '\r'; + const char LF = '\n'; + readonly BufferedStream innerStream; public HttpBufferedStream(Stream stream) { this.innerStream = new BufferedStream(stream); } + public override bool CanRead => this.innerStream.CanRead; + + public override bool CanSeek => this.innerStream.CanSeek; + + public override bool CanWrite => this.innerStream.CanWrite; + + public override long Length => this.innerStream.Length; + + public override long Position + { + get => this.innerStream.Position; + set => this.innerStream.Position = value; + } + public override void Flush() { this.innerStream.Flush(); @@ -45,7 +59,6 @@ public async Task ReadLineAsync(CancellationToken cancellationToken) var builder = new StringBuilder(); while (true) { - int length = await this.innerStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken) .ConfigureAwait(false); @@ -54,14 +67,14 @@ public async Task ReadLineAsync(CancellationToken cancellationToken) throw new IOException("Unexpected end of stream."); } - if (crFound && (char) buffer[position] == LF) + if (crFound && (char)buffer[position] == LF) { builder.Remove(builder.Length - 1, 1); return builder.ToString(); } - builder.Append((char) buffer[position]); - crFound = (char) buffer[position] == CR; + builder.Append((char)buffer[position]); + crFound = (char)buffer[position] == CR; } } @@ -85,19 +98,6 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati return this.innerStream.WriteAsync(buffer, offset, count, cancellationToken); } - public override bool CanRead => this.innerStream.CanRead; - public override bool CanSeek => this.innerStream.CanSeek; - - public override bool CanWrite => this.innerStream.CanWrite; - - public override long Length => this.innerStream.Length; - - public override long Position - { - get => this.innerStream.Position; - set => this.innerStream.Position = value; - } - protected override void Dispose(bool disposing) { this.innerStream.Dispose(); diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpRequestResponseSerializer.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpRequestResponseSerializer.cs index 83d93f645cd..6d9705ce021 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpRequestResponseSerializer.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpRequestResponseSerializer.cs @@ -11,21 +11,20 @@ namespace Microsoft.Azure.Devices.Edge.Util.Uds class HttpRequestResponseSerializer { - private const char SP = ' '; - private const char CR = '\r'; - private const char LF = '\n'; - private const char ProtocolVersionSeparator = '/'; - private const string Protocol = "HTTP"; - private const char HeaderSeparator = ':'; - private const string ContentLengthHeaderName = "content-length"; - + const char SP = ' '; + const char CR = '\r'; + const char LF = '\n'; + const char ProtocolVersionSeparator = '/'; + const string Protocol = "HTTP"; + const char HeaderSeparator = ':'; + const string ContentLengthHeaderName = "content-length"; public byte[] SerializeRequest(HttpRequestMessage request) { Preconditions.CheckNotNull(request, nameof(request)); Preconditions.CheckNotNull(request.RequestUri, nameof(request.RequestUri)); - PreProcessRequest(request); + this.PreProcessRequest(request); var builder = new StringBuilder(); // request-line = method SP request-target SP HTTP-version CRLF @@ -63,13 +62,13 @@ public async Task DeserializeResponse(HttpBufferedStream bu { var httpResponse = new HttpResponseMessage(); - await SetResponseStatusLine(httpResponse, bufferedStream, cancellationToken).ConfigureAwait(false); - await SetHeadersAndContent(httpResponse, bufferedStream, cancellationToken).ConfigureAwait(false); + await this.SetResponseStatusLine(httpResponse, bufferedStream, cancellationToken).ConfigureAwait(false); + await this.SetHeadersAndContent(httpResponse, bufferedStream, cancellationToken).ConfigureAwait(false); return httpResponse; } - private async Task SetHeadersAndContent(HttpResponseMessage httpResponse, HttpBufferedStream bufferedStream, CancellationToken cancellationToken) + async Task SetHeadersAndContent(HttpResponseMessage httpResponse, HttpBufferedStream bufferedStream, CancellationToken cancellationToken) { IList headers = new List(); string line = await bufferedStream.ReadLineAsync(cancellationToken).ConfigureAwait(false); @@ -106,6 +105,7 @@ private async Task SetHeadersAndContent(HttpResponseMessage httpResponse, HttpBu { throw new HttpRequestException($"Header value is invalid for {headerName}."); } + await httpResponse.Content.LoadIntoBufferAsync(contentLength).ConfigureAwait(false); } @@ -114,7 +114,7 @@ private async Task SetHeadersAndContent(HttpResponseMessage httpResponse, HttpBu } } - private async Task SetResponseStatusLine(HttpResponseMessage httpResponse, HttpBufferedStream bufferedStream, CancellationToken cancellationToken) + async Task SetResponseStatusLine(HttpResponseMessage httpResponse, HttpBufferedStream bufferedStream, CancellationToken cancellationToken) { string statusLine = await bufferedStream.ReadLineAsync(cancellationToken).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(statusLine)) @@ -145,7 +145,7 @@ private async Task SetResponseStatusLine(HttpResponseMessage httpResponse, HttpB httpResponse.ReasonPhrase = statusLineParts[2]; } - private void PreProcessRequest(HttpRequestMessage request) + void PreProcessRequest(HttpRequestMessage request) { if (string.IsNullOrEmpty(request.Headers.Host)) { diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpUdsMessageHandler.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpUdsMessageHandler.cs index f413af85c1f..fe72b9283e8 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpUdsMessageHandler.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/HttpUdsMessageHandler.cs @@ -9,9 +9,9 @@ namespace Microsoft.Azure.Devices.Edge.Util.Uds using System.Threading.Tasks; using Microsoft.Extensions.Logging; - internal class HttpUdsMessageHandler : HttpMessageHandler + class HttpUdsMessageHandler : HttpMessageHandler { - private readonly Uri providerUri; + readonly Uri providerUri; public HttpUdsMessageHandler(Uri providerUri) { @@ -39,7 +39,7 @@ protected override async Task SendAsync(HttpRequestMessage return response; } - private async Task GetConnectedSocketAsync() + async Task GetConnectedSocketAsync() { var endpoint = new UnixDomainSocketEndPoint(this.providerUri.LocalPath); var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); @@ -52,8 +52,8 @@ private async Task GetConnectedSocketAsync() static class Events { - static readonly ILogger Log = Logger.Factory.CreateLogger(); const int IdStart = UtilEventsIds.HttpUdsMessageHandler; + static readonly ILogger Log = Logger.Factory.CreateLogger(); enum EventIds { diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/UnixDomainSocketEndPoint.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/UnixDomainSocketEndPoint.cs index 73a5e2df3fd..d3ae7f70430 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/UnixDomainSocketEndPoint.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/uds/UnixDomainSocketEndPoint.cs @@ -1,27 +1,27 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Diagnostics; -using System.Text; -using System.Net; -using System.Net.Sockets; - namespace Microsoft.Azure.Devices.Edge.Util.Uds { - internal sealed class UnixDomainSocketEndPoint : EndPoint + using System; + using System.Diagnostics; + using System.Net; + using System.Net.Sockets; + using System.Text; + + sealed class UnixDomainSocketEndPoint : EndPoint { - private const AddressFamily EndPointAddressFamily = AddressFamily.Unix; + const AddressFamily EndPointAddressFamily = AddressFamily.Unix; - private static readonly Encoding s_pathEncoding = Encoding.UTF8; + static readonly Encoding s_pathEncoding = Encoding.UTF8; - private static readonly int s_nativePathOffset = 2; // = offsetof(struct sockaddr_un, sun_path). It's the same on Linux and OSX + static readonly int s_nativePathOffset = 2; // = offsetof(struct sockaddr_un, sun_path). It's the same on Linux and OSX - private static readonly int s_nativePathLength = 91; // sockaddr_un.sun_path at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_un.h.html, -1 for terminator + static readonly int s_nativePathLength = 91; // sockaddr_un.sun_path at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_un.h.html, -1 for terminator - private static readonly int s_nativeAddressSize = s_nativePathOffset + s_nativePathLength; + static readonly int s_nativeAddressSize = s_nativePathOffset + s_nativePathLength; - private readonly string _path; + readonly string _path; - private readonly byte[] _encodedPath; + readonly byte[] _encodedPath; public UnixDomainSocketEndPoint(string path) { @@ -30,10 +30,10 @@ public UnixDomainSocketEndPoint(string path) throw new ArgumentNullException(nameof(path)); } - _path = path; - _encodedPath = s_pathEncoding.GetBytes(_path); + this._path = path; + this._encodedPath = s_pathEncoding.GetBytes(this._path); - if (path.Length == 0 || _encodedPath.Length > s_nativePathLength) + if (path.Length == 0 || this._encodedPath.Length > s_nativePathLength) { throw new ArgumentOutOfRangeException(nameof(path), path); } @@ -54,39 +54,40 @@ internal UnixDomainSocketEndPoint(SocketAddress socketAddress) if (socketAddress.Size > s_nativePathOffset) { - _encodedPath = new byte[socketAddress.Size - s_nativePathOffset]; - for (int i = 0; i < _encodedPath.Length; i++) + this._encodedPath = new byte[socketAddress.Size - s_nativePathOffset]; + for (int i = 0; i < this._encodedPath.Length; i++) { - _encodedPath[i] = socketAddress[s_nativePathOffset + i]; + this._encodedPath[i] = socketAddress[s_nativePathOffset + i]; } - _path = s_pathEncoding.GetString(_encodedPath, 0, _encodedPath.Length); + this._path = s_pathEncoding.GetString(this._encodedPath, 0, this._encodedPath.Length); } else { - _encodedPath = Array.Empty(); - _path = string.Empty; + this._encodedPath = Array.Empty(); + this._path = string.Empty; } } + public override AddressFamily AddressFamily => EndPointAddressFamily; + public override SocketAddress Serialize() { var result = new SocketAddress(AddressFamily.Unix, s_nativeAddressSize); - Debug.Assert(_encodedPath.Length + s_nativePathOffset <= result.Size, "Expected path to fit in address"); + Debug.Assert(this._encodedPath.Length + s_nativePathOffset <= result.Size, "Expected path to fit in address"); - for (int index = 0; index < _encodedPath.Length; index++) + for (int index = 0; index < this._encodedPath.Length; index++) { - result[s_nativePathOffset + index] = _encodedPath[index]; + result[s_nativePathOffset + index] = this._encodedPath[index]; } - result[s_nativePathOffset + _encodedPath.Length] = 0; // path must be null-terminated + + result[s_nativePathOffset + this._encodedPath.Length] = 0; // path must be null-terminated return result; } public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress); - public override AddressFamily AddressFamily => EndPointAddressFamily; - - public override string ToString() => _path; + public override string ToString() => this._path; } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStorageRocksDbWTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStorageRocksDbWTest.cs index 77f7dd392f5..73eaca2da84 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStorageRocksDbWTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStorageRocksDbWTest.cs @@ -12,11 +12,11 @@ public class ColumnFamilyStorageRocksDbWTest [Fact] public void CreateWithNullPathThrowsAsync() { - //Arrange + // Arrange ICollection partitions = new List(); - //Act - //Assert + // Act + // Assert Assert.Throws(() => ColumnFamilyStorageRocksDbWrapper.Create(null, partitions)); partitions.Clear(); @@ -25,9 +25,9 @@ public void CreateWithNullPathThrowsAsync() [Fact] public void CreateWithNullPartitionsPathThrowsAsync() { - //Arrange - //Act - //Assert + // Arrange + // Act + // Assert Assert.Throws(() => ColumnFamilyStorageRocksDbWrapper.Create("AnyPath", null)); } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStoreTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStoreTest.cs index f7369e5faa2..36761a72f3c 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStoreTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/ColumnFamilyStoreTest.cs @@ -49,7 +49,7 @@ public async Task FirstLastTest() string firstValue = "firstValue"; await columnFamilyDbStore.Put(firstKey.ToBytes(), firstValue.ToBytes()); - for(int i = 0; i < 10; i++) + for (int i = 0; i < 10; i++) { string key = $"key{i}"; string value = "$value{i}"; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/DbStoreProviderTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/DbStoreProviderTest.cs index f262032c8c1..400110bb1c8 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/DbStoreProviderTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/DbStoreProviderTest.cs @@ -20,6 +20,7 @@ public DbStoreProviderTest() { Directory.Delete(this.rocksDbFolder); } + Directory.CreateDirectory(this.rocksDbFolder); } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test.csproj b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test.csproj index bbe6a09c8d3..0b5a1c8db32 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test.csproj +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test.csproj @@ -36,4 +36,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RockDbWrapperTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RockDbWrapperTest.cs index 501f5dd0d67..7eeb00d2baa 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RockDbWrapperTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RockDbWrapperTest.cs @@ -2,10 +2,10 @@ namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test { using System; + using System.Collections.Generic; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; - using System.Collections.Generic; [Unit] public class RockDbWrapperTest @@ -13,10 +13,10 @@ public class RockDbWrapperTest [Fact] public void CreateWithNullOptionThrowsAsync() { - //Arrange + // Arrange ICollection partitions = new List(); - //Act - //Assert + // Act + // Assert Assert.Throws(() => RocksDbWrapper.Create(null, "AnyPath", partitions)); partitions.Clear(); } @@ -24,11 +24,11 @@ public void CreateWithNullOptionThrowsAsync() [Fact] public void CreateWithNullPathThrowsAsync() { - //Arrange + // Arrange ICollection partitions = new List(); var options = new RocksDbOptionsProvider(new SystemEnvironment(), true); - //Act - //Assert + // Act + // Assert Assert.Throws(() => RocksDbWrapper.Create(options, null, partitions)); partitions.Clear(); } @@ -36,11 +36,11 @@ public void CreateWithNullPathThrowsAsync() [Fact] public void CreateWithNullPartitionsPathThrowsAsync() { - //Arrange + // Arrange var options = new RocksDbOptionsProvider(new SystemEnvironment(), true); - //Act - //Assert + // Act + // Assert Assert.Throws(() => RocksDbWrapper.Create(options, "AnyPath", null)); } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RocksDbOptionsProviderTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RocksDbOptionsProviderTest.cs index e3b4c74af93..952589a5529 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RocksDbOptionsProviderTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/RocksDbOptionsProviderTest.cs @@ -20,7 +20,7 @@ public void RocksDbOptionsProviderCreateTest() [Fact] public void RocksDbOptionsProviderCreatesDbOptions() { - //arrange + // arrange var env32 = new Mock(); env32.SetupGet(s => s.Is32BitProcess) .Returns(() => true); @@ -32,11 +32,11 @@ public void RocksDbOptionsProviderCreatesDbOptions() var provider32 = new RocksDbOptionsProvider(env32.Object, true); var provider64 = new RocksDbOptionsProvider(env64.Object, true); - //act + // act DbOptions newOptions32 = provider32.GetDbOptions(); DbOptions newOptions64 = provider64.GetDbOptions(); - //assert + // assert Assert.NotNull(newOptions32); Assert.NotNull(newOptions64); } @@ -55,11 +55,11 @@ public void RocksDbOptionsProviderCreateCfOptions() var provider32 = new RocksDbOptionsProvider(env32.Object, true); var provider64 = new RocksDbOptionsProvider(env64.Object, true); - //act + // act ColumnFamilyOptions newOptions32 = provider32.GetColumnFamilyOptions(); ColumnFamilyOptions newOptions64 = provider64.GetColumnFamilyOptions(); - //assert + // assert Assert.NotNull(newOptions32); Assert.NotNull(newOptions64); } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/TestRocksDbStoreProvider.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/TestRocksDbStoreProvider.cs index f837c083aeb..6a480e17036 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/TestRocksDbStoreProvider.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test/TestRocksDbStoreProvider.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.RocksDb.Test { using System; using System.IO; - using Microsoft.Azure.Devices.Edge.Storage.RocksDb; using Microsoft.Azure.Devices.Edge.Util; public class TestRocksDbStoreProvider : IDisposable @@ -20,6 +19,7 @@ public TestRocksDbStoreProvider() { Directory.Delete(this.rocksDbFolder); } + Directory.CreateDirectory(this.rocksDbFolder); this.rocksDbStoreProvider = DbStoreProvider.Create(options, this.rocksDbFolder, new string[0]); } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EncryptedStoreTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EncryptedStoreTest.cs index c40dc2f8db2..73b35c819aa 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EncryptedStoreTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EncryptedStoreTest.cs @@ -108,6 +108,18 @@ await encryptedStore.IterateBatch( static IEntityStore GetEntityStore(string entityName) => new EntityStore(new InMemoryDbStore(), entityName); + public class KeyAuth + { + [JsonConstructor] + public KeyAuth(string key) + { + this.Key = key; + } + + [JsonProperty("key")] + public string Key { get; } + } + class TestDevice { [JsonConstructor] @@ -124,18 +136,6 @@ public TestDevice(string genId, KeyAuth auth) public KeyAuth Auth { get; } } - public class KeyAuth - { - [JsonConstructor] - public KeyAuth(string key) - { - this.Key = key; - } - - [JsonProperty("key")] - public string Key { get; } - } - class TestEncryptionProvider : IEncryptionProvider { public Task DecryptAsync(string encryptedText) => Task.FromResult(Encoding.UTF8.GetString(Convert.FromBase64String(encryptedText))); diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTest.cs index 2572f2c1942..716c8f49ef2 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTest.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Storage.Test { - using Microsoft.Azure.Devices.Edge.Storage; - public class EntityStoreTest : EntityStoreTestBase { protected override IEntityStore GetEntityStore(string entityName) => new EntityStore(new InMemoryDbStore(), entityName); diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTestBase.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTestBase.cs index a6afc93a57c..75524085de5 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTestBase.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/EntityStoreTestBase.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test { using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test; using Microsoft.Azure.Devices.Edge.Util.Test.Common; @@ -12,8 +11,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test [Integration] public abstract class EntityStoreTestBase { - protected abstract IEntityStore GetEntityStore(string entityName); - [Fact] [TestPriority(301)] public async Task BasicTest() @@ -26,7 +23,7 @@ public async Task BasicTest() await entityStore.Put(new Key { Type = "B", Id = 2 }, new Value { Prop1 = "Bar", Prop2 = 20 }); await entityStore.Put(new Key { Type = "B", Id = 3 }, new Value { Prop1 = "Bar", Prop2 = 30 }); - //act/assert to validate key. + // act/assert to validate key. Option<(Key key, Value value)> itemRetrieved = await entityStore.GetFirstEntry(); Assert.True(itemRetrieved.HasValue); @@ -37,7 +34,6 @@ public async Task BasicTest() Assert.Equal(1, pair.key.Id); }); - itemRetrieved = await entityStore.GetLastEntry(); Assert.True(itemRetrieved.HasValue); itemRetrieved.ForEach( @@ -123,15 +119,19 @@ public async Task UpdateTest() Assert.Equal(getUpdatedValue.OrDefault(), updatedValue); } + protected abstract IEntityStore GetEntityStore(string entityName); + public class Key { public string Type { get; set; } + public int Id { get; set; } } public class Value { public string Prop1 { get; set; } + public int Prop2 { get; set; } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/InMemoryStorePartitionTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/InMemoryStorePartitionTest.cs index 2a44a911864..a1b89135a59 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/InMemoryStorePartitionTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/InMemoryStorePartitionTest.cs @@ -3,14 +3,13 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test { using System; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; [Unit] public class InMemoryStorePartitionTest - { + { [Fact] public async Task GetMultipleOperationsTest() { @@ -26,7 +25,7 @@ public async Task GetMultipleOperationsTest() Task removeTask = Task.Run(async () => await inMemoryStorePartition.Remove("key0".ToBytes())); Task putTask = Task.Run(async () => await inMemoryStorePartition.Put("key1".ToBytes(), "newvalue".ToBytes())); - await Task.WhenAll(removeTask, putTask, getTask); + await Task.WhenAll(removeTask, putTask, getTask); Assert.True(getTask.IsCompletedSuccessfully); Assert.True(removeTask.IsCompletedSuccessfully); Assert.True(putTask.IsCompletedSuccessfully); @@ -63,22 +62,28 @@ public async Task IterateBatchTest() } int[] counter = { 11 }; - await inMemoryStorePartition.IterateBatch($"key{counter[0]}".ToBytes(), 20, (key, value) => - { - Assert.Equal($"key{counter[0]}", key.FromBytes()); - Assert.Equal($"val{counter[0]}", value.FromBytes()); - counter[0]++; - return Task.CompletedTask; - }); + await inMemoryStorePartition.IterateBatch( + $"key{counter[0]}".ToBytes(), + 20, + (key, value) => + { + Assert.Equal($"key{counter[0]}", key.FromBytes()); + Assert.Equal($"val{counter[0]}", value.FromBytes()); + counter[0]++; + return Task.CompletedTask; + }); counter[0] = 61; - await inMemoryStorePartition.IterateBatch($"key{counter[0]}".ToBytes(), 20, (key, value) => - { - Assert.Equal($"key{counter[0]}", key.FromBytes()); - Assert.Equal($"val{counter[0]}", value.FromBytes()); - counter[0]++; - return Task.CompletedTask; - }); + await inMemoryStorePartition.IterateBatch( + $"key{counter[0]}".ToBytes(), + 20, + (key, value) => + { + Assert.Equal($"key{counter[0]}", key.FromBytes()); + Assert.Equal($"val{counter[0]}", value.FromBytes()); + counter[0]++; + return Task.CompletedTask; + }); } [Fact] @@ -86,11 +91,13 @@ public async Task IterateEmptyBatchTest() { var inMemoryStorePartition = new InMemoryDbStore(); bool callbackCalled = false; - await inMemoryStorePartition.IterateBatch(20, (key, value) => - { - callbackCalled = true; - return Task.CompletedTask; - }); + await inMemoryStorePartition.IterateBatch( + 20, + (key, value) => + { + callbackCalled = true; + return Task.CompletedTask; + }); Assert.False(callbackCalled); } @@ -105,24 +112,23 @@ public async Task UpdateDuringIterateTest() await inMemoryStorePartition.Put($"key{i}".ToBytes(), $"val{i}".ToBytes()); } - Task iterateBatch = Task.Run(async () => - { - await inMemoryStorePartition.IterateBatch( - 10, - async (key, value) => - { - await Task.Delay(TimeSpan.FromMilliseconds(500)); - }); - }); - - Task updateTask = Task.Run(async () => - { - await inMemoryStorePartition.Remove("key0".ToBytes()); - await Task.Delay(TimeSpan.FromMilliseconds(500)); - await inMemoryStorePartition.Put("key20".ToBytes(), "newValue".ToBytes()); - await Task.Delay(TimeSpan.FromMilliseconds(500)); - await inMemoryStorePartition.Remove("key8".ToBytes()); - }); + Task iterateBatch = Task.Run( + async () => + { + await inMemoryStorePartition.IterateBatch( + 10, + async (key, value) => { await Task.Delay(TimeSpan.FromMilliseconds(500)); }); + }); + + Task updateTask = Task.Run( + async () => + { + await inMemoryStorePartition.Remove("key0".ToBytes()); + await Task.Delay(TimeSpan.FromMilliseconds(500)); + await inMemoryStorePartition.Put("key20".ToBytes(), "newValue".ToBytes()); + await Task.Delay(TimeSpan.FromMilliseconds(500)); + await inMemoryStorePartition.Remove("key8".ToBytes()); + }); await Task.WhenAll(updateTask, iterateBatch); diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/Microsoft.Azure.Devices.Edge.Storage.Test.csproj b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/Microsoft.Azure.Devices.Edge.Storage.Test.csproj index 4f4bee37d85..f578fb93597 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/Microsoft.Azure.Devices.Edge.Storage.Test.csproj +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/Microsoft.Azure.Devices.Edge.Storage.Test.csproj @@ -34,4 +34,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTest.cs index 826396e6a47..cdf4cb7425b 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTest.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Storage.Test { - using Microsoft.Azure.Devices.Edge.Storage; - public class SequentialStoreTest : SequentialStoreTestBase { protected override IEntityStore GetEntityStore(string entityName) => new EntityStore(new InMemoryDbStore(), entityName); diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTestBase.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTestBase.cs index 210aba3aba8..ad13b45a39d 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTestBase.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SequentialStoreTestBase.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; @@ -13,8 +12,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test [Integration] public abstract class SequentialStoreTestBase { - protected abstract IEntityStore GetEntityStore(string entityName); - [Fact] public async Task CreateTest() { @@ -158,7 +155,7 @@ public async Task GetBatchTest(Option defaultHeadOffset) long startOffset = defaultHeadOffset.GetOrElse(0); ISequentialStore sequentialStore = await this.GetSequentialStore(entityId, defaultHeadOffset); - // Try to get the batch, should return empty batch. + // Try to get the batch, should return empty batch. List<(long, Item)> batch = (await sequentialStore.GetBatch(startOffset, 10)).ToList(); Assert.NotNull(batch); Assert.Equal(0, batch.Count); @@ -175,7 +172,7 @@ public async Task GetBatchTest(Option defaultHeadOffset) await sequentialStore.RemoveFirst((o, itm) => Task.FromResult(true)); } - // Try to get with starting offset <= 4, should return remaining elements - 4 - 9. + // Try to get with starting offset <= 4, should return remaining elements - 4 - 9. for (int i = 0; i <= 4; i++) { batch = (await sequentialStore.GetBatch(i + startOffset, 10)).ToList(); @@ -184,7 +181,7 @@ public async Task GetBatchTest(Option defaultHeadOffset) Assert.Equal(4 + startOffset, batch[0].Item1); } - // Try to get with starting offset between 5 and 9, should return a valid batch. + // Try to get with starting offset between 5 and 9, should return a valid batch. for (int i = 5; i < 10; i++) { batch = (await sequentialStore.GetBatch(i + startOffset, 10)).ToList(); @@ -202,10 +199,12 @@ public async Task GetBatchTest(Option defaultHeadOffset) } } + protected abstract IEntityStore GetEntityStore(string entityName); + static IEnumerable GetDefaultHeadOffset() { yield return new object[] { Option.None() }; - yield return new object[] { Option.Some((long)1500) }; + yield return new object[] { Option.Some(1500L) }; } async Task> GetSequentialStore(string entityName, Option defaultHeadOffset) diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SerDeExtensionsTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SerDeExtensionsTest.cs index 402b56d9af5..e4c851ab381 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SerDeExtensionsTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/SerDeExtensionsTest.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Storage.Test { - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Newtonsoft.Json; using Xunit; @@ -9,10 +8,17 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test [Unit] public class SerDeExtensionsTest { + public interface ITestInterface + { + string GetProp1(); + + long GetProp2(); + } + [Fact] public void ToJsonTest() { - Assert.Equal("", SerDeExtensions.ToJson(null)); + Assert.Equal(string.Empty, SerDeExtensions.ToJson(null)); string str = "Foo Bar"; Assert.Equal(str, SerDeExtensions.ToJson(str)); @@ -32,10 +38,10 @@ public void ToJsonTest() public void FromJsonTest() { Assert.Null(SerDeExtensions.FromJson(null)); - Assert.Null(SerDeExtensions.FromJson("")); + Assert.Null(SerDeExtensions.FromJson(string.Empty)); Assert.Equal(0, SerDeExtensions.FromJson(null)); - Assert.Equal(0, SerDeExtensions.FromJson("")); + Assert.Equal(0, SerDeExtensions.FromJson(string.Empty)); string str = "Foo Bar"; Assert.Equal(str, SerDeExtensions.FromJson(str)); @@ -65,8 +71,8 @@ public void StringToBytesTest() [Fact] public void FromBytesTest() { - Assert.Equal("", SerDeExtensions.FromBytes(null)); - Assert.Equal("", SerDeExtensions.FromBytes(new byte[0])); + Assert.Equal(string.Empty, SerDeExtensions.FromBytes(null)); + Assert.Equal(string.Empty, SerDeExtensions.FromBytes(new byte[0])); } [Fact] @@ -98,26 +104,21 @@ public void ObjectToBytesRoundtripTest() Assert.Equal(testObj.GetProp2(), testObj2.GetProp2()); } - public interface ITestInterface - { - string GetProp1(); - long GetProp2(); - } - class TestClass : ITestInterface { + [JsonConstructor] + public TestClass(string prop1) + { + this.Prop1 = prop1; + } + public string Prop1 { get; } + public long Prop2 { get; set; } public string GetProp1() => this.Prop1; public long GetProp2() => this.Prop2; - - [JsonConstructor] - public TestClass(string prop1) - { - this.Prop1 = prop1; - } } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/StoreUtilsTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/StoreUtilsTest.cs index 3958b210483..d3856279914 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/StoreUtilsTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Storage.Test/StoreUtilsTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Storage.Test { using System.Linq; - using Microsoft.Azure.Devices.Edge.Storage; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/CertificateHelper.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/CertificateHelper.cs index 2725b5be890..fde1fc4dc74 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/CertificateHelper.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/CertificateHelper.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common { using System; using System.Collections.Generic; - using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X509; @@ -13,7 +12,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common using Org.BouncyCastle.Crypto.Prng; using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; - using Org.BouncyCastle.X509; using Org.BouncyCastle.X509.Extension; using BCX509 = Org.BouncyCastle.X509; @@ -108,22 +106,25 @@ public enum ExtKeyUsage None = 0, ClientAuth, ServerAuth, - }; + } - public static X509Certificate2 GetCertificate(string thumbprint, - StoreName storeName, + public static X509Certificate2 GetCertificate( + string thumbprint, + StoreName storeName, StoreLocation storeLocation) { Preconditions.CheckNonWhiteSpace(thumbprint, nameof(thumbprint)); var store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.ReadOnly); - X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, - thumbprint, + X509Certificate2Collection col = store.Certificates.Find( + X509FindType.FindByThumbprint, + thumbprint, false); if (col != null && col.Count > 0) { return col[0]; } + return null; } @@ -142,20 +143,22 @@ public static (X509Certificate2, AsymmetricCipherKeyPair) GenerateServerCert(str public static (X509Certificate2, AsymmetricCipherKeyPair) GenerateClientert(string subjectName, DateTime notBefore, DateTime notAfter) => GenerateCertificate(subjectName, notBefore, notAfter, null, null, false, null, new List() { ExtKeyUsage.ClientAuth }); - public static (X509Certificate2, AsymmetricCipherKeyPair) GenerateCertificate(string subjectName, - DateTime notBefore, - DateTime notAfter, - X509Certificate2 issuer, - AsymmetricCipherKeyPair issuerKeyPair, - bool isCA, - GeneralNames sanEntries, - IList extKeyUsages) + public static (X509Certificate2, AsymmetricCipherKeyPair) GenerateCertificate( + string subjectName, + DateTime notBefore, + DateTime notAfter, + X509Certificate2 issuer, + AsymmetricCipherKeyPair issuerKeyPair, + bool isCA, + GeneralNames sanEntries, + IList extKeyUsages) { if (((issuer == null) && (issuerKeyPair != null)) || ((issuer != null) && (issuerKeyPair == null))) { throw new ArgumentException("Issuer and Issuer key pair must both be null or non null"); } + var keyGenerator = new RsaKeyPairGenerator(); var random = new SecureRandom(new CryptoApiRandomGenerator()); keyGenerator.Init(new KeyGenerationParameters(random, 1024)); @@ -164,7 +167,7 @@ public static (X509Certificate2, AsymmetricCipherKeyPair) GenerateCertificate(st var certName = new X509Name($"CN={subjectName}"); BigInteger serialNo = BigInteger.ProbablePrime(120, random); - var certGenerator = new X509V3CertificateGenerator(); + var certGenerator = new BCX509.X509V3CertificateGenerator(); certGenerator.SetSerialNumber(serialNo); certGenerator.SetSubjectDN(certName); certGenerator.SetNotAfter(notAfter); @@ -206,10 +209,11 @@ public static (X509Certificate2, AsymmetricCipherKeyPair) GenerateCertificate(st oids.Add(new DerObjectIdentifier("1.3.6.1.5.5.7.3.1")); } } + var ext = new ExtendedKeyUsage(oids); certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, ext); } - + var privateKey = (issuerKeyPair == null) ? keyPair.Private : issuerKeyPair.Private; var signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", privateKey, random); BCX509.X509Certificate bcCert = certGenerator.Generate(signatureFactory); @@ -225,6 +229,7 @@ public static GeneralNames PrepareSanEntries(IList uris, IList d { throw new ArgumentException($"Total entries count is zero. uris:{uris.Count}, dnsNames:{dnsNames.Count}"); } + GeneralName[] names = new GeneralName[totalCount]; int index = 0; @@ -232,10 +237,12 @@ public static GeneralNames PrepareSanEntries(IList uris, IList d { names[index++] = new GeneralName(GeneralName.UniformResourceIdentifier, value); } + foreach (string value in dnsNames) { names[index++] = new GeneralName(GeneralName.DnsName, value); } + GeneralNames subjectAltNames = new GeneralNames(names); return subjectAltNames; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ClientAssertionCertificate.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ClientAssertionCertificate.cs index 97ccff74916..b24eae081b7 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ClientAssertionCertificate.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ClientAssertionCertificate.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test.Common { - using Microsoft.IdentityModel.Clients.ActiveDirectory; - using Microsoft.IdentityModel.Tokens; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; + using Microsoft.IdentityModel.Clients.ActiveDirectory; + using Microsoft.IdentityModel.Tokens; class ClientAssertionCertificate : IClientAssertionCertificate { @@ -16,7 +16,7 @@ public ClientAssertionCertificate(string clientId, X509Certificate2 clientAssert this.ClientId = clientId; this.certificate = clientAssertionCertPfx; } - + public string ClientId { get; } public string Thumbprint => Base64UrlEncoder.Encode(this.certificate.GetCertHash()); @@ -29,4 +29,4 @@ public byte[] Sign(string message) } } } -} \ No newline at end of file +} diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ConfigHelper.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ConfigHelper.cs index e3b14f0ca64..f72abcbd7d8 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ConfigHelper.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/ConfigHelper.cs @@ -46,6 +46,7 @@ static TestEnvironment GetEnvironment() { throw new InvalidOperationException($"Invalid test environment specified: {environmentName}"); } + return environment; } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/KeyVaultHelper.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/KeyVaultHelper.cs index 38df55d58be..1045faf27dd 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/KeyVaultHelper.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/KeyVaultHelper.cs @@ -3,7 +3,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common { using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.KeyVault; using Microsoft.Azure.KeyVault.Models; using Microsoft.IdentityModel.Clients.ActiveDirectory; @@ -26,7 +25,7 @@ public KeyVaultHelper(string clientId, X509Certificate2 clientAssertionCert) Preconditions.CheckNonWhiteSpace(clientId, nameof(clientId)); Preconditions.CheckNotNull(clientAssertionCert, nameof(clientAssertionCert)); this.clientId = clientId; - this.clientAssertionCert = clientAssertionCert; + this.clientAssertionCert = clientAssertionCert; } public async Task GetSecret(string secretUrl) @@ -47,12 +46,19 @@ public async Task GetSecret(string vaultBaseUrl, string secretName) return secretBundle.Value; } + static IClientAssertionCertificate GetAssertionCert(string clientId, X509Certificate2 clientAssertionCert) + { + var assertionCert = new ClientAssertionCertificate(clientId, clientAssertionCert); + return assertionCert; + } + KeyVaultClient GetKeyVaultClient() { if (!this.keyVaultClient.HasValue) { this.keyVaultClient = Option.Some(new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(this.GetAccessToken))); } + return this.keyVaultClient.OrDefault(); } @@ -62,13 +68,8 @@ IClientAssertionCertificate GetClientAssertionCertificate() { this.assertionCert = Option.Some(GetAssertionCert(this.clientId, this.clientAssertionCert)); } - return this.assertionCert.OrDefault(); - } - static IClientAssertionCertificate GetAssertionCert(string clientId, X509Certificate2 clientAssertionCert) - { - var assertionCert = new ClientAssertionCertificate(clientId, clientAssertionCert); - return assertionCert; + return this.assertionCert.OrDefault(); } async Task GetAccessToken(string authority, string resource, string scope) diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/Microsoft.Azure.Devices.Edge.Util.Test.Common.csproj b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/Microsoft.Azure.Devices.Edge.Util.Test.Common.csproj index e7815abf768..1b77fda4891 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/Microsoft.Azure.Devices.Edge.Util.Test.Common.csproj +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/Microsoft.Azure.Devices.Edge.Util.Test.Common.csproj @@ -52,4 +52,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/StringExtensions.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/StringExtensions.cs index fb5a61d6e2f..5b9240ce9fc 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/StringExtensions.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/StringExtensions.cs @@ -8,6 +8,8 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common public static class StringExtensions { + public const string LowerCaseAlphabet = "abcdefghijklmnopqrstuvwyxz"; + public static string RemoveWhitespace(this string input) => new string(input.Where(ch => !char.IsWhiteSpace(ch)).ToArray()); @@ -16,8 +18,6 @@ public static string RemoveWhitespace(this string input) => public static byte[] ToBody(this string input) => Encoding.UTF8.GetBytes(input.RemoveWhitespace().SingleToDoubleQuotes()); - public const string LowerCaseAlphabet = "abcdefghijklmnopqrstuvwyxz"; - public static string GenerateUniqueString(IEnumerable existingStrings, int size, Random rng) => GenerateUniqueString(existingStrings, size, rng, LowerCaseAlphabet); @@ -28,7 +28,8 @@ public static string GenerateUniqueString(IEnumerable existingStrings, i do { str = GenerateString(size, rng, alphabet); - } while (existingStringsAsList.Contains(str)); + } + while (existingStringsAsList.Contains(str)); return str; } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/TokenHelper.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/TokenHelper.cs index a7b25cf8661..dc71b74ad52 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/TokenHelper.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/TokenHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test.Common { using System; @@ -8,8 +8,8 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common public static class TokenHelper { - public static string CreateSasToken(string resourceUri, string key = null, bool expired = false) => - CreateSasToken(resourceUri, (expired ? new DateTime(2010, 1, 1) : new DateTime(2020, 1, 1)), key); + public static string CreateSasToken(string resourceUri, string key = null, bool expired = false) => + CreateSasToken(resourceUri, expired ? new DateTime(2010, 1, 1) : new DateTime(2020, 1, 1), key); public static string CreateSasToken(string resourceUri, DateTime expiryTime, string key = null) { @@ -32,7 +32,8 @@ static string GetRandomKey(int length = 45) { sb.Append('a' + rand.Next(0, 25)); } + return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/settings/linux.json b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/settings/linux.json index c1518d9723a..2f811f4e442 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/settings/linux.json +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test.Common/settings/linux.json @@ -1,4 +1,4 @@ { - "dockerHostUrl": "unix://var/run/docker.sock", - "enableWebSocketsTests": true + "dockerHostUrl": "unix://var/run/docker.sock", + "enableWebSocketsTests": true } \ No newline at end of file diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/CollectionExTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/CollectionExTest.cs index 5839ff6ff69..c8bc4523278 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/CollectionExTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/CollectionExTest.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test { + using System; using System.Collections.Generic; using System.Linq; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; - using System; [Unit] public class CollectionExTest @@ -175,7 +174,7 @@ public void TryGetNonEmptyValueTest() var stringDictionary = new Dictionary { ["1"] = "Foo", - ["2"] = "", + ["2"] = string.Empty, ["3"] = " ", ["4"] = null, }; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DictionaryComparerTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DictionaryComparerTest.cs index a0b28d47df5..dd6956b9bd1 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DictionaryComparerTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DictionaryComparerTest.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test { - using Microsoft.Azure.Devices.Edge.Util.Test.Common; using System; using System.Collections.Generic; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; [Unit] diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DiskFileTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DiskFileTest.cs index dca120f4a2d..c03960f9ec3 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DiskFileTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/DiskFileTest.cs @@ -28,9 +28,9 @@ public void Dispose() [Unit] public async Task InvalidInputFails() { - await Assert.ThrowsAsync(() => DiskFile.ReadAllAsync("")); - await Assert.ThrowsAsync(() => DiskFile.WriteAllAsync("", "test")); - await Assert.ThrowsAsync(() => DiskFile.WriteAllAsync("temp", "")); + await Assert.ThrowsAsync(() => DiskFile.ReadAllAsync(string.Empty)); + await Assert.ThrowsAsync(() => DiskFile.WriteAllAsync(string.Empty, "test")); + await Assert.ThrowsAsync(() => DiskFile.WriteAllAsync("temp", string.Empty)); } [Fact] diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ExceptionExTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ExceptionExTest.cs index a614e77e012..21cbe04eb48 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ExceptionExTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ExceptionExTest.cs @@ -8,6 +8,14 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test public class ExceptionExTest { + [Theory] + [MemberData(nameof(GetTestTimeoutExceptions))] + [Unit] + public void TestHasTimeoutException(Exception ex, bool hasTimeoutException) + { + Assert.Equal(hasTimeoutException, ex.HasTimeoutException()); + } + static IEnumerable GetTestTimeoutExceptions() { yield return new object[] { new TimeoutException(), true }; @@ -15,16 +23,8 @@ static IEnumerable GetTestTimeoutExceptions() yield return new object[] { new AggregateException(new InvalidOperationException(), new TimeoutException()), true }; yield return new object[] { new AggregateException(new InvalidOperationException(), new InvalidOperationException("foo", new InvalidOperationException("bar", new TimeoutException()))), true }; yield return new object[] { new InvalidOperationException(), false }; - yield return new object[] { new InvalidOperationException("foo", new InvalidOperationException("bar", new ArgumentException("abc"))), false}; + yield return new object[] { new InvalidOperationException("foo", new InvalidOperationException("bar", new ArgumentException("abc"))), false }; yield return new object[] { new AggregateException(new InvalidOperationException(), new ArgumentException()), false }; } - - [Theory] - [MemberData(nameof(GetTestTimeoutExceptions))] - [Unit] - public void TestHasTimeoutException(Exception ex, bool hasTimeoutException) - { - Assert.Equal(hasTimeoutException, ex.HasTimeoutException()); - } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/FallbackTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/FallbackTest.cs index e9b24bba714..932039e171b 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/FallbackTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/FallbackTest.cs @@ -16,7 +16,11 @@ public async Task FallbackInvokesPrimaryNotSecondary() bool secondary = false; Try result = await Fallback.ExecuteAsync( () => Task.FromResult(1), - () => { secondary = true; return Task.FromResult(2); }); + () => + { + secondary = true; + return Task.FromResult(2); + }); Assert.True(result.Success); Assert.Equal(1, result.Value); Assert.False(secondary); @@ -53,9 +57,10 @@ public async Task FallbackFailsIfPrimaryAndSecondaryThrow() [Fact] public async Task FallbackThrowsIfPrimaryThrowsFatalException() { - await Assert.ThrowsAsync(() => Fallback.ExecuteAsync( - () => throw new OutOfMemoryException(), - () => Task.FromResult(2))); + await Assert.ThrowsAsync( + () => Fallback.ExecuteAsync( + () => throw new OutOfMemoryException(), + () => Task.FromResult(2))); } [Fact] @@ -63,13 +68,29 @@ public async Task FallbackFunctionsCanReturnPlainTask() { int touched = 0; await Fallback.ExecuteAsync( - () => { ++touched; return Task.CompletedTask; }, - () => { ++touched; return Task.CompletedTask; }); + () => + { + ++touched; + return Task.CompletedTask; + }, + () => + { + ++touched; + return Task.CompletedTask; + }); Assert.Equal(1, touched); await Fallback.ExecuteAsync( - () => { ++touched; throw new Exception(); }, - () => { ++touched; return Task.CompletedTask; }); + () => + { + ++touched; + throw new Exception(); + }, + () => + { + ++touched; + return Task.CompletedTask; + }); Assert.Equal(3, touched); Try result = await Fallback.ExecuteAsync( @@ -77,9 +98,17 @@ await Fallback.ExecuteAsync( () => throw new Exception(), () => throw new Exception(), () => throw new Exception(), - () => { ++touched; return Task.CompletedTask; }, + () => + { + ++touched; + return Task.CompletedTask; + }, () => throw new Exception(), - () => { ++touched; return Task.CompletedTask; }); + () => + { + ++touched; + return Task.CompletedTask; + }); Assert.True(result.Success); Assert.True(result.Value); Assert.Equal(4, touched); @@ -121,6 +150,8 @@ public async Task FallbackFailsIfMultipleOptionsAllThrow() Assert.True(aggregateException.InnerExceptions.Contains(exception3)); } - class SecondaryException : Exception { } + class SecondaryException : Exception + { + } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/HashTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/HashTest.cs index 861fa88975b..bf221e3acc6 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/HashTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/HashTest.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test { - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/JsonExTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/JsonExTest.cs index 10950f5a76f..d07c77ca119 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/JsonExTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/JsonExTest.cs @@ -15,23 +15,25 @@ public class JsonExTest public void TestStripMetadata() { // Arrange - JToken input = JToken.FromObject(new Dictionary - { - { "foo", 10 }, - { "bar", 20 }, - { "$metadata", new { baz = 30 } }, - { "$version", 40 } - }); + JToken input = JToken.FromObject( + new Dictionary + { + { "foo", 10 }, + { "bar", 20 }, + { "$metadata", new { baz = 30 } }, + { "$version", 40 } + }); // Act JsonEx.StripMetadata(input); // Assert - JToken expected = JToken.FromObject(new - { - foo = 10, - bar = 20 - }); + JToken expected = JToken.FromObject( + new + { + foo = 10, + bar = 20 + }); Assert.True(JToken.DeepEquals(expected, input)); } @@ -40,34 +42,37 @@ public void TestStripMetadata() public void TestStripMetadata2() { // Arrange - JToken input = JToken.FromObject(new Dictionary - { - { "foo", 10 }, - { "bar", 20 }, - { "$metadata", new { baz = 30 } }, - { "$version", 40 }, - { "dontStripThis", new Dictionary + JToken input = JToken.FromObject( + new Dictionary + { + { "foo", 10 }, + { "bar", 20 }, + { "$metadata", new { baz = 30 } }, + { "$version", 40 }, { - { "$metadata", new { baz = 30 } }, - { "$version", 40 } + "dontStripThis", new Dictionary + { + { "$metadata", new { baz = 30 } }, + { "$version", 40 } + } } - } - }); + }); // Act JsonEx.StripMetadata(input); // Assert - JToken expected = JToken.FromObject(new - { - foo = 10, - bar = 20, - dontStripThis = new Dictionary + JToken expected = JToken.FromObject( + new { - { "$metadata", new { baz = 30 } }, - { "$version", 40 } - } - }); + foo = 10, + bar = 20, + dontStripThis = new Dictionary + { + { "$metadata", new { baz = 30 } }, + { "$version", 40 } + } + }); Assert.True(JToken.DeepEquals(expected, input)); } @@ -268,7 +273,7 @@ public void TestDiffAllCases() { name = new { - //["level0"] = "nochange", // unchanged + // ["level0"] = "nochange", // unchanged level1 = (Dictionary)null, // existing in base. remove property level2 = new { @@ -352,7 +357,7 @@ public void TestDiffAllCases() public void ChunkedPropertyTest(string field, string input, string expected) { var obj = JObject.Parse(input); - var chunked = obj.ChunkedValue(field, false).Select(t => t.ToString()).Join(""); + var chunked = obj.ChunkedValue(field, false).Select(t => t.ToString()).Join(string.Empty); Assert.Equal(expected, chunked); } @@ -362,7 +367,7 @@ public void ChunkedPropertyTest(string field, string input, string expected) public void ChunkedPropertyIgnoreCaseTest(string field, string input, string expected) { var obj = JObject.Parse(input); - var chunked = obj.ChunkedValue(field, true).Select(t => t.ToString()).Join(""); + var chunked = obj.ChunkedValue(field, true).Select(t => t.ToString()).Join(string.Empty); Assert.Equal(expected, chunked); } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/KeyVaultHelperTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/KeyVaultHelperTest.cs index 92fc860b7de..39ea1bdb6b2 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/KeyVaultHelperTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/KeyVaultHelperTest.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test { - using Microsoft.Azure.Devices.Edge.Util.Test.Common; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; public class KeyVaultHelperTest diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/LinqExTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/LinqExTest.cs index 4337bfa0142..8c28bbdda72 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/LinqExTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/LinqExTest.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Azure.Devices.Edge.Util.Test { - using Microsoft.Azure.Devices.Edge.Util.Test.Common; using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; public class LinqExTest @@ -66,20 +66,24 @@ public void TestRemoveIntersectionKeysDefault() }; IEnumerable result1 = seq1.RemoveIntersectionKeys(seq2); - Assert.True(result1.SequenceEqual(new[] - { - "k1=v1", - "k4=v4" - })); + Assert.True( + result1.SequenceEqual( + new[] + { + "k1=v1", + "k4=v4" + })); IEnumerable result2 = seq2.RemoveIntersectionKeys(seq1); - Assert.True(result2.SequenceEqual(new[] - { - "pk1=v1", - "pk2=v2", - "pk3=v3", - "pk4=v4" - })); + Assert.True( + result2.SequenceEqual( + new[] + { + "pk1=v1", + "pk2=v2", + "pk3=v3", + "pk4=v4" + })); } [Fact] @@ -111,24 +115,28 @@ public void TestRemoveIntersectionKeysWithKeySelector() Func keySelector = s => s; IEnumerable result1 = seq1.RemoveIntersectionKeys(seq2, keySelector); - Assert.True(result1.SequenceEqual(new[] - { - "k1=v1", - "k2=v2", - "k3=v3", - "k4=v4" - })); + Assert.True( + result1.SequenceEqual( + new[] + { + "k1=v1", + "k2=v2", + "k3=v3", + "k4=v4" + })); IEnumerable result2 = seq2.RemoveIntersectionKeys(seq1, keySelector); - Assert.True(result2.SequenceEqual(new[] - { - "pk1=v1", - "pk2=v2", - "k2=v21", - "pk3=v3", - "pk4=v4", - "k3=v32" - })); + Assert.True( + result2.SequenceEqual( + new[] + { + "pk1=v1", + "pk2=v2", + "k2=v21", + "pk3=v3", + "pk4=v4", + "k3=v32" + })); } [Fact] @@ -138,9 +146,9 @@ public void EnumerateTest() var strings = new List { "zero", "one", "two" }; var expected = new List<(uint, string)> { - { (0, "zero") }, - { (1, "one") }, - { (2, "two") }, + { (0, "zero") }, + { (1, "one") }, + { (2, "two") }, }; Assert.Equal(expected, strings.Enumerate().ToList()); } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/Microsoft.Azure.Devices.Edge.Util.Test.csproj b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/Microsoft.Azure.Devices.Edge.Util.Test.csproj index 8c9586f5de8..3e99167281e 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/Microsoft.Azure.Devices.Edge.Util.Test.csproj +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/Microsoft.Azure.Devices.Edge.Util.Test.csproj @@ -45,4 +45,11 @@ + + + + + ..\..\..\stylecop.ruleset + + diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/OptionTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/OptionTest.cs index c7ff90fa05f..c2184020a5e 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/OptionTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/OptionTest.cs @@ -90,6 +90,7 @@ public void TestEnumerable() { i += value; } + Assert.Equal(6, i); int count = 0; @@ -98,6 +99,7 @@ public void TestEnumerable() { count++; } + Assert.Equal(0, count); Assert.Equal(1, some.ToEnumerable().Count()); @@ -231,19 +233,21 @@ public async void TestForEachAsync() int i = 2; // ReSharper disable once AccessToModifiedClosure // Need to test the side effect - await some.ForEachAsync(v => - { - i *= v; - return Task.CompletedTask; - }); + await some.ForEachAsync( + v => + { + i *= v; + return Task.CompletedTask; + }); Assert.Equal(6, i); i = 2; - await none.ForEachAsync(v => - { - i *= v; - return Task.CompletedTask; - }); + await none.ForEachAsync( + v => + { + i *= v; + return Task.CompletedTask; + }); Assert.Equal(2, i); } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PreconditionsTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PreconditionsTest.cs index 7f6e05c25f3..907b4bfe1ef 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PreconditionsTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PreconditionsTest.cs @@ -72,9 +72,9 @@ public void TestCheckRange() public void TestCheckNonWhiteS() { Assert.Throws(() => Preconditions.CheckNonWhiteSpace(" ", "param1")); - Assert.Throws(() => Preconditions.CheckNonWhiteSpace("", "param2")); + Assert.Throws(() => Preconditions.CheckNonWhiteSpace(string.Empty, "param2")); Preconditions.CheckNonWhiteSpace(" foo ", "param3"); Preconditions.CheckNonWhiteSpace(" b", "param4"); } } -} \ No newline at end of file +} diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PriorityOrderer.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PriorityOrderer.cs index c0532f2f4bb..2c4cc78498f 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PriorityOrderer.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/PriorityOrderer.cs @@ -10,12 +10,12 @@ public class PriorityOrderer : ITestCaseOrderer { const string PriorityPropertyName = "Priority"; - public IEnumerable OrderTestCases(IEnumerable testCases) where TTestCase : ITestCase + public IEnumerable OrderTestCases(IEnumerable testCases) + where TTestCase : ITestCase { (int Priority, TTestCase TestCase) Selector(TTestCase t) => - (Priority: t.TestMethod.Method.GetCustomAttributes((typeof(TestPriorityAttribute).AssemblyQualifiedName)) - .FirstOrDefault()?.GetNamedArgument(PriorityPropertyName) ?? 0, - TestCase: t); + (t.TestMethod.Method.GetCustomAttributes(typeof(TestPriorityAttribute).AssemblyQualifiedName) + .FirstOrDefault()?.GetNamedArgument(PriorityPropertyName) ?? 0, t); return testCases .Select(Selector) @@ -23,5 +23,4 @@ public IEnumerable OrderTestCases(IEnumerable t .Select(t => t.TestCase); } } - } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ResettableTimerTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ResettableTimerTest.cs index 45f1cff2bc1..7caccbb7e66 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ResettableTimerTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/ResettableTimerTest.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Moq; using Xunit; [Unit] @@ -16,6 +15,7 @@ public async Task CreateAndStartTimerTest() { // Arrange int callbackCalledCount = 0; + Task Callback() { Interlocked.Increment(ref callbackCalledCount); @@ -38,6 +38,7 @@ public async Task ResetTimerTest() { // Arrange int callbackCalledCount = 0; + Task Callback() { Interlocked.Increment(ref callbackCalledCount); @@ -66,6 +67,7 @@ public async Task DisableEnableTimerTest() { // Arrange int callbackCalledCount = 0; + Task Callback() { Interlocked.Increment(ref callbackCalledCount); @@ -75,7 +77,7 @@ Task Callback() TimeSpan period = TimeSpan.FromSeconds(3); var resettableTimer = new ResettableTimer(Callback, period, null); - // Act + // Act resettableTimer.Start(); await Task.Delay(TimeSpan.FromSeconds(4)); @@ -96,7 +98,6 @@ Task Callback() // Assert Assert.Equal(2, callbackCalledCount); - } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/RetryTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/RetryTest.cs index 6fd61da5adf..3ea85468788 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/RetryTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/RetryTest.cs @@ -2,10 +2,10 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test { using System; - using Microsoft.Azure.Devices.Edge.Util.Test.Common; - using Xunit; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Devices.Edge.Util.Test.Common; + using Xunit; [Unit] public class RetryTest @@ -28,15 +28,14 @@ public async Task RetryRetriesFuncUntilValidResultIsReturned() [Fact] public async Task RetryThrowsIfFuncNeverReturnsValidResult() { - Func> func = () => Task.FromResult(String.Empty); + Func> func = () => Task.FromResult(string.Empty); Func isValid = (val) => val == "Foo"; TimeSpan retryInterval = TimeSpan.FromMilliseconds(10); using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200))) { await Assert.ThrowsAsync( - () => Retry.Do(func, isValid, null, retryInterval, cts.Token) - ); + () => Retry.Do(func, isValid, null, retryInterval, cts.Token)); } } @@ -44,7 +43,11 @@ await Assert.ThrowsAsync( public async Task RetryWithoutValidFuncReturns1stResult() { int counter = 0; - Func> func = () => { ++counter; return Task.FromResult("Foo"); }; + Func> func = () => + { + ++counter; + return Task.FromResult("Foo"); + }; TimeSpan retryInterval = TimeSpan.FromMilliseconds(2); using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5))) @@ -64,8 +67,7 @@ public async Task RetryWithoutExceptionFuncThrowsIfFuncThrows() using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5))) { await Assert.ThrowsAsync( - () => Retry.Do(func, null, null, retryInterval, cts.Token) - ); + () => Retry.Do(func, null, null, retryInterval, cts.Token)); } } @@ -94,8 +96,7 @@ public async Task RetryThrowsIfExceptionFuncReturnsFalse() using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5))) { await Assert.ThrowsAsync( - () => Retry.Do(func, null, continueOnException, retryInterval, cts.Token) - ); + () => Retry.Do(func, null, continueOnException, retryInterval, cts.Token)); } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/SasTokenHelperTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/SasTokenHelperTest.cs index 5d4602566cc..990495a0511 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/SasTokenHelperTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/SasTokenHelperTest.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test { using System; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/TaskExTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/TaskExTest.cs index 590eb084b7d..0364acf5c34 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/TaskExTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/TaskExTest.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test using System; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Devices.Edge.Util; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Xunit; diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/certificate/CertificateHelperTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/certificate/CertificateHelperTest.cs index c4efac2e265..5d5596c60d7 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/certificate/CertificateHelperTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/certificate/CertificateHelperTest.cs @@ -2,8 +2,8 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Certificate { using System; - using System.IO; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Certificate using Xunit; using CertificateHelper = Microsoft.Azure.Devices.Edge.Util.CertificateHelper; using TestCertificateHelper = Microsoft.Azure.Devices.Edge.Util.Test.Common.CertificateHelper; - using System.Security.Cryptography; [Unit] public class CertificateHelperTest @@ -65,10 +64,12 @@ public void ValidateCertNoMatchFailure() public void ClientCertCallbackNullArgumentThrows() { var trustedCACerts = Option.None>(); - Assert.Throws(() => - CertificateHelper.ValidateClientCert(null, new List(), trustedCACerts, Logger.Factory.CreateLogger("something"))); - Assert.Throws(() => - CertificateHelper.ValidateClientCert(new X509Certificate2(), null, trustedCACerts, Logger.Factory.CreateLogger("something"))); + Assert.Throws( + () => + CertificateHelper.ValidateClientCert(null, new List(), trustedCACerts, Logger.Factory.CreateLogger("something"))); + Assert.Throws( + () => + CertificateHelper.ValidateClientCert(new X509Certificate2(), null, trustedCACerts, Logger.Factory.CreateLogger("something"))); } [Fact] @@ -80,12 +81,11 @@ public void ClientCertCallbackNoCaCertsFails() Assert.False(CertificateHelper.ValidateClientCert(cert, new List(), trustedCACerts, Logger.Factory.CreateLogger("something"))); } - [Fact] public void ExtractCertsNullArgumentFails() { Assert.Throws(() => CertificateHelper.ExtractCertsFromPem(null)); - Assert.Throws(() => CertificateHelper.ExtractCertsFromPem("")); + Assert.Throws(() => CertificateHelper.ExtractCertsFromPem(string.Empty)); } [Fact] @@ -93,7 +93,7 @@ public void GetServerCertificateAndChainFromFileRaisesArgExceptionWithInvalidCer { string testFile = Path.GetRandomFileName(); Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(null, testFile)); - Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile("", testFile)); + Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(string.Empty, testFile)); Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(" ", testFile)); } @@ -102,7 +102,7 @@ public void GetServerCertificateAndChainFromFileRaisesArgExceptionWithInvalidPri { string testFile = Path.GetRandomFileName(); Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(testFile, null)); - Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(testFile, "")); + Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(testFile, string.Empty)); Assert.Throws(() => CertificateHelper.GetServerCertificateAndChainFromFile(testFile, " ")); } @@ -111,7 +111,7 @@ public void ParseTrustedBundleFromFileRaisesExceptionWithInvalidTBFile() { string testFile = Path.GetRandomFileName(); Assert.Throws(() => CertificateHelper.ParseTrustedBundleFromFile(null)); - Assert.Throws(() => CertificateHelper.ParseTrustedBundleFromFile("")); + Assert.Throws(() => CertificateHelper.ParseTrustedBundleFromFile(string.Empty)); Assert.Throws(() => CertificateHelper.ParseTrustedBundleFromFile(" ")); Assert.Throws(() => CertificateHelper.ParseTrustedBundleFromFile(testFile)); } @@ -297,7 +297,7 @@ public void TestValidateCertificateWithExpiredValidityFails() { var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2)); var notAfter = DateTime.Now.Subtract(TimeSpan.FromDays(1)); - var (clientCert, clientKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestClient", notBefore, notAfter, false); + var (clientCert, clientKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestClient", notBefore, notAfter, false); Assert.False(CertificateHelper.ValidateClientCert(clientCert, new List() { clientCert }, Option.None>(), Logger.Factory.CreateLogger("something"))); } @@ -330,18 +330,19 @@ public void TestValidateCertificateAndChainSucceeds() Assert.True(CertificateHelper.ValidateClientCert(issuedClientCert, new List() { caCert }, Option.None>(), Logger.Factory.CreateLogger("something"))); } - //TODO need to discuss test failure - //[Fact] - //public void TestValidateCertificateAndChainFails() - //{ - // var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2)); - // var notAfter = DateTime.Now.AddYears(1); - // var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true); - // var (clientCert, clientKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestClient", notBefore, notAfter, false); - // var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false); + /*TODO need to discuss test failure + [Fact] + public void TestValidateCertificateAndChainFails() + { + var notBefore = DateTime.Now.Subtract(TimeSpan.FromDays(2)); + var notAfter = DateTime.Now.AddYears(1); + var (caCert, caKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestCA", notBefore, notAfter, true); + var (clientCert, clientKeyPair) = TestCertificateHelper.GenerateSelfSignedCert("MyTestClient", notBefore, notAfter, false); + var (issuedClientCert, issuedClientKeyPair) = TestCertificateHelper.GenerateCertificate("MyIssuedTestClient", notBefore, notAfter, caCert, caKeyPair, false); - // Assert.False(CertificateHelper.ValidateClientCert(issuedClientCert, new List() { clientCert }, Option.None>(), Logger.Factory.CreateLogger("something"))); - //} + Assert.False(CertificateHelper.ValidateClientCert(issuedClientCert, new List() { clientCert }, Option.None>(), Logger.Factory.CreateLogger("something"))); + } + */ [Fact] public void TestValidateTrustedCACertificateAndChainSucceeds() diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/concurrency/AsyncLockTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/concurrency/AsyncLockTest.cs index 67853228aca..4d0a2278b42 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/concurrency/AsyncLockTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/concurrency/AsyncLockTest.cs @@ -29,14 +29,15 @@ public async Task TestLockedPreventsLockUntilUnlocked() var locked = new TaskCompletionSource(); var cont = new TaskCompletionSource(); - Task t1 = Task.Run(async () => - { - using (await mutex.LockAsync()) + Task t1 = Task.Run( + async () => { - locked.SetResult(true); - await cont.Task; - } - }); + using (await mutex.LockAsync()) + { + locked.SetResult(true); + await cont.Task; + } + }); await locked.Task; Task> t2Start = Task.Factory.StartNew(async () => await mutex.LockAsync()); @@ -56,21 +57,23 @@ public async Task TestDoubleDisposeOnlyPermitsOneTask() var t1HasLock = new TaskCompletionSource(); var t1Continue = new TaskCompletionSource(); - await Task.Run(async () => - { - AsyncLock.Releaser key = await mutex.LockAsync(); - key.Dispose(); - key.Dispose(); - }); + await Task.Run( + async () => + { + AsyncLock.Releaser key = await mutex.LockAsync(); + key.Dispose(); + key.Dispose(); + }); - Task t1 = Task.Run(async () => - { - using (await mutex.LockAsync()) + Task t1 = Task.Run( + async () => { - t1HasLock.SetResult(true); - await t1Continue.Task; - } - }); + using (await mutex.LockAsync()) + { + t1HasLock.SetResult(true); + await t1Continue.Task; + } + }); await t1HasLock.Task; Task> task2Start = Task.Factory.StartNew(async () => await mutex.LockAsync()); @@ -115,4 +118,4 @@ async Task F1() await Assert.ThrowsAsync(F1); } } -} \ No newline at end of file +} diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/edged/WorkloadClientTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/edged/WorkloadClientTest.cs index 23380849bee..0adc9178333 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/edged/WorkloadClientTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/edged/WorkloadClientTest.cs @@ -17,11 +17,11 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Edged [Unit] public class WorkloadClientTest : IClassFixture { - readonly Uri serverUri; const string WorkloadApiVersion = "2018-06-28"; const string ModuleId = "testModule"; const string ModulegenerationId = "1"; const string Iv = "7826a0b7e6de4a84bd39c8e69c46d2a6"; + readonly Uri serverUri; public WorkloadClientTest(WorkloadFixture workloadFixture) { @@ -32,9 +32,9 @@ public WorkloadClientTest(WorkloadFixture workloadFixture) public void InstantiateInvalidArgumentsShouldThrow() { Assert.Throws(() => new WorkloadClient(null, WorkloadApiVersion, ModuleId, ModulegenerationId)); - Assert.Throws(() => new WorkloadClient(this.serverUri, "", ModuleId, ModulegenerationId)); - Assert.Throws(() => new WorkloadClient(this.serverUri, WorkloadApiVersion, "", ModulegenerationId)); - Assert.Throws(() => new WorkloadClient(this.serverUri, WorkloadApiVersion, ModuleId, "")); + Assert.Throws(() => new WorkloadClient(this.serverUri, string.Empty, ModuleId, ModulegenerationId)); + Assert.Throws(() => new WorkloadClient(this.serverUri, WorkloadApiVersion, string.Empty, ModulegenerationId)); + Assert.Throws(() => new WorkloadClient(this.serverUri, WorkloadApiVersion, ModuleId, string.Empty)); } [Fact] diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/json/OptionConverterTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/json/OptionConverterTest.cs index d117cdc8e1e..1cbb0eca4be 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/json/OptionConverterTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/json/OptionConverterTest.cs @@ -10,43 +10,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Json [Unit] public class OptionConverterTest { - class TestObject : IEquatable - { - [JsonConverter(typeof(OptionConverter))] - [JsonProperty(PropertyName = "value")] - public Option Value { get; } - - [JsonConstructor] - public TestObject(string value) - { - this.Value = Option.Maybe(value); - } - - public bool Equals(TestObject other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - return ReferenceEquals(this, other) || this.Value.Equals(other.Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - return obj.GetType() == this.GetType() && this.Equals((TestObject)obj); - } - - public override int GetHashCode() => this.Value.GetHashCode(); - } - [Fact] public void TestSerializeWithValue() { @@ -94,5 +57,45 @@ public void TestDeserializeWithoutValue() Assert.Equal(expected, t1); Assert.Equal(Option.None(), t1.Value); } + + class TestObject : IEquatable + { + [JsonConstructor] + public TestObject(string value) + { + this.Value = Option.Maybe(value); + } + + [JsonConverter(typeof(OptionConverter))] + [JsonProperty(PropertyName = "value")] + public Option Value { get; } + + public bool Equals(TestObject other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + return ReferenceEquals(this, other) || this.Value.Equals(other.Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == this.GetType() && this.Equals((TestObject)obj); + } + + public override int GetHashCode() => this.Value.GetHashCode(); + } } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpBufferedStreamTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpBufferedStreamTest.cs index 77b262fc229..4176cbce8a2 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpBufferedStreamTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpBufferedStreamTest.cs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.Azure.Devices.Edge.Util.Test.uds +namespace Microsoft.Azure.Devices.Edge.Util.Test.Uds { using System.Collections.Generic; using System.IO; using System.Text; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Edge.Util.Test.Common; using Microsoft.Azure.Devices.Edge.Util.Uds; @@ -16,15 +17,15 @@ public class HttpBufferedStreamTest public async Task TestReadLines_ShouldReturnResponse() { string expected = "GET /modules/testModule/sign?api-version=2018-06-28 HTTP/1.1\r\nHost: localhost:8081\r\nConnection: close\r\nContent-Type: application/json\r\nContent-Length: 100\r\n\r\n"; - + byte[] expectedBytes = Encoding.UTF8.GetBytes(expected); var memory = new MemoryStream(expectedBytes, true); IList lines = new List(); var buffered = new HttpBufferedStream(memory); - System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken); + CancellationToken cancellationToken = default(CancellationToken); string line = await buffered.ReadLineAsync(cancellationToken); - + while (!string.IsNullOrEmpty(line)) { lines.Add(line); @@ -38,6 +39,5 @@ public async Task TestReadLines_ShouldReturnResponse() Assert.Equal("Content-Type: application/json", lines[3]); Assert.Equal("Content-Length: 100", lines[4]); } - } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpRequestResponseSerializerTest.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpRequestResponseSerializerTest.cs index 56c7cd6d5da..9d8420572de 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpRequestResponseSerializerTest.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/uds/HttpRequestResponseSerializerTest.cs @@ -111,7 +111,7 @@ public void TestSerializeRequest_ShouldSerializeRequest() AssertNormalizedValues(expected, actual); } - + [Fact] public void TestDeserializeResponse_InvalidEndOfStream_ShouldThrow() { @@ -274,8 +274,8 @@ public async Task TestDeserializeResponse_ValidContent_ShouldDeserialize() static void AssertNormalizedValues(string expected, string actual) { // Remove metacharacters before assertion to allow to run on both Windows and Linux; which Linux will return additional carriage return character. - string normalizedExpected = Regex.Replace(expected, @"\s", "").ToLower(); - string normalizedActual = Regex.Replace(actual, @"\s", "").ToLower(); + string normalizedExpected = Regex.Replace(expected, @"\s", string.Empty).ToLower(); + string normalizedActual = Regex.Replace(actual, @"\s", string.Empty).ToLower(); Assert.Equal(normalizedExpected, normalizedActual); } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/Startup.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/Startup.cs index 82065269c58..270be562fa3 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/Startup.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/Startup.cs @@ -11,7 +11,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadFixture.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadFixture.cs index e8d2d0267fc..761dd5a7272 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadFixture.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadFixture.cs @@ -9,8 +9,17 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common.WorkloadTestServer public class WorkloadFixture : IDisposable { + public readonly string ServiceUrl = ServiceHost.Instance.Url; const int DefaultPort = 50003; - public string ServiceUrl = ServiceHost.Instance.Url; + + #region IDisposable Support + + // Don't dispose the Server, in case another test thread is using it. + public void Dispose() + { + } + + #endregion class ServiceHost { @@ -19,7 +28,7 @@ class ServiceHost ServiceHost() { - this.webHostTask = BuildWebHost(new string[0], DefaultPort).RunAsync(cancellationTokenSource.Token); + this.webHostTask = BuildWebHost(new string[0], DefaultPort).RunAsync(this.cancellationTokenSource.Token); this.Url = $"http://localhost:{DefaultPort}"; } @@ -29,15 +38,9 @@ class ServiceHost static IWebHost BuildWebHost(string[] args, int port) => WebHost.CreateDefaultBuilder(args) - .UseUrls($"http://*:{port}") - .UseStartup() - .Build(); + .UseUrls($"http://*:{port}") + .UseStartup() + .Build(); } - - #region IDisposable Support - // Don't dispose the Server, in case another test thread is using it. - public void Dispose() - { } - #endregion } } diff --git a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadTestImplementation.cs b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadTestImplementation.cs index 9289296645a..6c9aac56cc9 100644 --- a/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadTestImplementation.cs +++ b/edge-util/test/Microsoft.Azure.Devices.Edge.Util.Test/workloadtestserver/WorkloadTestImplementation.cs @@ -2,7 +2,6 @@ namespace Microsoft.Azure.Devices.Edge.Util.Test.Common.WorkloadTestServer { using System; - using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -35,13 +34,13 @@ public Task DecryptAsync(string api_version, string name, strin return Task.FromResult(new DecryptResponse() { Plaintext = Encoding.UTF8.GetBytes(dencrypted) }); } - public Task CreateIdentityCertificateAsync(string api_version, string name, string genid) => throw new System.NotImplementedException(); + public Task CreateIdentityCertificateAsync(string api_version, string name, string genid) => throw new NotImplementedException(); public Task CreateServerCertificateAsync(string api_version, string name, string genid, ServerCertificateRequest request) { var response = new CertificateResponse() { - Certificate = $"{CertificateHelper.CertificatePem}\n{CertificateHelper.CertificatePem}", + Certificate = $"{CertificateHelper.CertificatePem}\n{CertificateHelper.CertificatePem}", PrivateKey = new PrivateKey() { Type = PrivateKeyType.Key, diff --git a/smoke/IotEdgeQuickstart/IotEdgeQuickstart.csproj b/smoke/IotEdgeQuickstart/IotEdgeQuickstart.csproj index 2c1206ae579..eeffbeb6ee8 100644 --- a/smoke/IotEdgeQuickstart/IotEdgeQuickstart.csproj +++ b/smoke/IotEdgeQuickstart/IotEdgeQuickstart.csproj @@ -34,4 +34,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/smoke/IotEdgeQuickstart/Program.cs b/smoke/IotEdgeQuickstart/Program.cs index e71dc5ab7df..8d6ab40ef2a 100644 --- a/smoke/IotEdgeQuickstart/Program.cs +++ b/smoke/IotEdgeQuickstart/Program.cs @@ -56,14 +56,10 @@ Option Default value --runtime-log-level debug --clean_up_existing_device false --proxy No proxy is used -" - )] +")] [HelpOption] class Program { - // ReSharper disable once UnusedMember.Local - static int Main(string[] args) => CommandLineApplication.ExecuteAsync(args).Result; - [Option("-a|--bootstrapper-archive ", Description = "Path to bootstrapper archive")] public string BootstrapperArchivePath { get; } = Environment.GetEnvironmentVariable("bootstrapperArchivePath"); @@ -119,13 +115,13 @@ class Program public string DeploymentFileName { get; } = Environment.GetEnvironmentVariable("deployment"); [Option("--device_ca_cert", Description = "path to the device ca certificate and its chain")] - public string DeviceCaCert { get; } = ""; + public string DeviceCaCert { get; } = string.Empty; [Option("--device_ca_pk", Description = "path to the device ca private key file")] - public string DeviceCaPk { get; } = ""; + public string DeviceCaPk { get; } = string.Empty; [Option("--trusted_ca_certs", Description = "path to a file containing all the trusted CA")] - public string DeviceCaCerts { get; } = ""; + public string DeviceCaCerts { get; } = string.Empty; [Option("--clean_up_existing_device ", CommandOptionType.SingleValue, Description = "Clean up existing device on success.")] public bool CleanUpExistingDeviceOnSuccess { get; } = false; @@ -136,6 +132,9 @@ class Program [Option("--upstream-protocol ", CommandOptionType.SingleValue, Description = "Upstream protocol for IoT Hub connections.")] public (bool overrideUpstreamProtocol, UpstreamProtocolType upstreamProtocol) UpstreamProtocol { get; } = (false, UpstreamProtocolType.Amqp); + // ReSharper disable once UnusedMember.Local + static int Main(string[] args) => CommandLineApplication.ExecuteAsync(args).Result; + // ReSharper disable once UnusedMember.Local async Task OnExecuteAsync() { @@ -158,31 +157,30 @@ async Task OnExecuteAsync() switch (this.BootstrapperType) { case BootstrapperType.Iotedged: + (bool useProxy, string proxyUrl) = this.Proxy; + Option proxy = useProxy + ? Option.Some(proxyUrl) + : Option.Maybe(Environment.GetEnvironmentVariable("https_proxy")); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - (bool useProxy, string proxyUrl) = this.Proxy; - Option proxy = useProxy - ? Option.Some(proxyUrl) - : Option.Maybe(Environment.GetEnvironmentVariable("https_proxy")); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - bootstrapper = new IotedgedWindows(this.BootstrapperArchivePath, credentials, proxy); - } - else - { - (bool useHttp, string hostname) = this.UseHttp; - Option uris = useHttp - ? Option.Some(string.IsNullOrEmpty(hostname) ? new HttpUris() : new HttpUris(hostname)) - : Option.None(); - - (bool overrideUpstreamProtocol, UpstreamProtocolType upstreamProtocol) = this.UpstreamProtocol; - Option upstreamProtocolOption = overrideUpstreamProtocol - ? Option.Some(upstreamProtocol) - : Option.None(); - - bootstrapper = new IotedgedLinux(this.BootstrapperArchivePath, credentials, uris, proxy, upstreamProtocolOption); - } + bootstrapper = new IotedgedWindows(this.BootstrapperArchivePath, credentials, proxy); } + else + { + (bool useHttp, string hostname) = this.UseHttp; + Option uris = useHttp + ? Option.Some(string.IsNullOrEmpty(hostname) ? new HttpUris() : new HttpUris(hostname)) + : Option.None(); + + (bool overrideUpstreamProtocol, UpstreamProtocolType upstreamProtocol) = this.UpstreamProtocol; + Option upstreamProtocolOption = overrideUpstreamProtocol + ? Option.Some(upstreamProtocol) + : Option.None(); + + bootstrapper = new IotedgedLinux(this.BootstrapperArchivePath, credentials, uris, proxy, upstreamProtocolOption); + } + break; case BootstrapperType.Iotedgectl: bootstrapper = new Iotedgectl(this.BootstrapperArchivePath, credentials); @@ -192,10 +190,10 @@ async Task OnExecuteAsync() } string connectionString = this.IotHubConnectionString ?? - await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); + await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); string endpoint = this.EventHubCompatibleEndpointWithEntityPath ?? - await SecretsHelper.GetSecretFromConfigKey("eventHubConnStrKey"); + await SecretsHelper.GetSecretFromConfigKey("eventHubConnStrKey"); Option deployment = this.DeploymentFileName != null ? Option.Some(this.DeploymentFileName) : Option.None(); @@ -213,7 +211,7 @@ async Task OnExecuteAsync() this.LeaveRunning, this.NoDeployment, this.NoVerify, - this.VerifyDataFromModule, + this.VerifyDataFromModule, deployment, this.DeviceCaCert, this.DeviceCaPk, @@ -239,7 +237,6 @@ async Task OnExecuteAsync() // key - based on registry hostname (e.g., // edgerelease.azurecr.io => edgerelease-azurecr-io) // value - " " (separated by a space) - string key = address.Replace('.', '-'); string value = await SecretsHelper.GetSecret(key); string[] vals = value.Split(' ', StringSplitOptions.RemoveEmptyEntries); @@ -255,9 +252,9 @@ public enum BootstrapperType public enum LeaveRunning { - All, // don't clean up anything + All, // don't clean up anything Core, // remove modules/identities except Edge Agent & Hub - None // iotedgectl stop, uninstall, remove device identity + None // iotedgectl stop, uninstall, remove device identity } public enum LogLevel diff --git a/smoke/IotEdgeQuickstart/Quickstart.cs b/smoke/IotEdgeQuickstart/Quickstart.cs index 8c6a88bcb28..f7865f758a6 100644 --- a/smoke/IotEdgeQuickstart/Quickstart.cs +++ b/smoke/IotEdgeQuickstart/Quickstart.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -// ReSharper disable ArrangeThisQualifier namespace IotEdgeQuickstart { using System; @@ -33,8 +32,8 @@ public Quickstart( string deviceCaCerts, bool optimizedForPerformance, LogLevel runtimeLogLevel, - bool cleanUpExistingDeviceOnSuccess) : - base(bootstrapper, credentials, iothubConnectionString, eventhubCompatibleEndpointWithEntityPath, upstreamProtocol, imageTag, deviceId, hostname, deploymentFileName, deviceCaCert, deviceCaPk, deviceCaCerts, optimizedForPerformance, runtimeLogLevel, cleanUpExistingDeviceOnSuccess) + bool cleanUpExistingDeviceOnSuccess) + : base(bootstrapper, credentials, iothubConnectionString, eventhubCompatibleEndpointWithEntityPath, upstreamProtocol, imageTag, deviceId, hostname, deploymentFileName, deviceCaCert, deviceCaPk, deviceCaCerts, optimizedForPerformance, runtimeLogLevel, cleanUpExistingDeviceOnSuccess) { this.leaveRunning = leaveRunning; this.noDeployment = noDeployment; @@ -48,24 +47,23 @@ public async Task RunAsync() // its config. This could happen, for example, if someone were to create an at-scale deployment on the // test hub with the target condition: "NOT deviceId=''". Since this is an unlikely scenario, we won't // invest the effort to guard against it. - - await VerifyEdgeIsNotAlreadyActive(); // don't accidentally overwrite an edge configuration on a dev machine - await VerifyBootstrapperDependencies(); - await InstallBootstrapper(); + await this.VerifyEdgeIsNotAlreadyActive(); // don't accidentally overwrite an edge configuration on a dev machine + await this.VerifyBootstrapperDependencies(); + await this.InstallBootstrapper(); try { - await GetOrCreateEdgeDeviceIdentity(); - await ConfigureBootstrapper(); + await this.GetOrCreateEdgeDeviceIdentity(); + await this.ConfigureBootstrapper(); try { - await StartBootstrapper(); - await VerifyEdgeAgentIsRunning(); - await VerifyEdgeAgentIsConnectedToIotHub(); + await this.StartBootstrapper(); + await this.VerifyEdgeAgentIsRunning(); + await this.VerifyEdgeAgentIsConnectedToIotHub(); if (!this.noDeployment) { - await DeployToEdgeDevice(); + await this.DeployToEdgeDevice(); if (!this.noVerify) { await this.VerifyDataOnIoTHub(this.verifyDataFromModule); @@ -73,23 +71,23 @@ public async Task RunAsync() if (this.leaveRunning == LeaveRunning.Core) { - await RemoveTempSensorFromEdgeDevice(); + await this.RemoveTempSensorFromEdgeDevice(); } } } - catch(Exception e) + catch (Exception e) { Console.WriteLine("** Oops, there was a problem. We'll stop the IoT Edge runtime, but we'll leave it configured so you can investigate."); Console.WriteLine($"Exception: {e}"); - KeepEdgeDeviceIdentity(); - await StopBootstrapper(); + this.KeepEdgeDeviceIdentity(); + await this.StopBootstrapper(); throw; } if (this.leaveRunning == LeaveRunning.None) { - await StopBootstrapper(); - await ResetBootstrapper(); + await this.StopBootstrapper(); + await this.ResetBootstrapper(); } } finally @@ -97,7 +95,7 @@ public async Task RunAsync() if (this.leaveRunning == LeaveRunning.None) { // only remove the identity if we created it; if it already existed in IoT Hub then leave it alone - await MaybeDeleteEdgeDeviceIdentity(); + await this.MaybeDeleteEdgeDeviceIdentity(); } } } diff --git a/smoke/IotEdgeQuickstart/details/Details.cs b/smoke/IotEdgeQuickstart/details/Details.cs index 081424b4d44..72ed6b7afc2 100644 --- a/smoke/IotEdgeQuickstart/details/Details.cs +++ b/smoke/IotEdgeQuickstart/details/Details.cs @@ -19,22 +19,112 @@ namespace IotEdgeQuickstart.Details public class Details { + public readonly Option DeploymentFileName; + + const string DeployJson = @" +{ + ""modulesContent"": { + ""$edgeAgent"": { + ""properties.desired"": { + ""schemaVersion"": ""1.0"", + ""runtime"": { + ""type"": ""docker"", + ""settings"": { + ""minDockerVersion"": ""v1.25"", + ""loggingOptions"": """" + } + }, + ""systemModules"": { + ""edgeAgent"": { + ""type"": ""docker"", + ""settings"": { + ""image"": """", + ""createOptions"": """" + } + }, + ""edgeHub"": { + ""type"": ""docker"", + ""status"": ""running"", + ""restartPolicy"": ""always"", + ""settings"": { + ""image"": """", + ""createOptions"": ""{\""HostConfig\"":{\""PortBindings\"":{\""8883/tcp\"":[{\""HostPort\"":\""8883\""}],\""443/tcp\"":[{\""HostPort\"":\""443\""}],\""5671/tcp\"":[{\""HostPort\"":\""5671\""}]}}}"" + }, + ""env"": { + ""OptimizeForPerformance"": { + ""value"": """" + } + }, + } + }, + ""modules"": { + ""tempSensor"": { + ""version"": ""1.0"", + ""type"": ""docker"", + ""status"": ""running"", + ""restartPolicy"": ""always"", + ""settings"": { + ""image"": """", + ""createOptions"": """" + } + } + } + } + }, + ""$edgeHub"": { + ""properties.desired"": { + ""schemaVersion"": ""1.0"", + ""routes"": { + ""route"": ""FROM /* INTO $upstream"" + }, + ""storeAndForwardConfiguration"": { + ""timeToLiveSecs"": 7200 + } + } + } + } +} +"; + + const string DeployJsonRegistry = @" + ,""registryCredentials"": { + ""registry"": { + ""address"": """", + ""username"": """", + ""password"": """" + } + } +"; + readonly IBootstrapper bootstrapper; + readonly Option credentials; + readonly string iothubConnectionString; + readonly string eventhubCompatibleEndpointWithEntityPath; + readonly ServiceClientTransportType serviceClientTransportType; + readonly EventHubClientTransportType eventHubClientTransportType; + readonly string imageTag; + readonly string deviceId; + readonly string hostname; - public readonly Option DeploymentFileName; + readonly string deviceCaCert; + readonly string deviceCaPk; + readonly string deviceCaCerts; + readonly bool optimizedForPerformance; + readonly LogLevel runtimeLogLevel; - readonly bool cleanUpExistingDeviceOnSuccess; + + readonly bool cleanUpExistingDeviceOnSuccess; DeviceContext context; @@ -53,8 +143,7 @@ protected Details( string deviceCaCerts, bool optimizedForPerformance, LogLevel runtimeLogLevel, - bool cleanUpExistingDeviceOnSuccess - ) + bool cleanUpExistingDeviceOnSuccess) { this.bootstrapper = bootstrapper; this.credentials = credentials; @@ -122,28 +211,6 @@ protected async Task GetOrCreateEdgeDeviceIdentity() } } - async Task CreateEdgeDeviceIdentity(RegistryManager rm) - { - var device = new Device(this.deviceId) - { - Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas }, - Capabilities = new DeviceCapabilities() { IotEdge = true } - }; - - IotHubConnectionStringBuilder builder = IotHubConnectionStringBuilder.Create(this.iothubConnectionString); - Console.WriteLine($"Registering device '{device.Id}' on IoT hub '{builder.HostName}'"); - - device = await rm.AddDeviceAsync(device); - - this.context = new DeviceContext - { - Device = device, - IotHubConnectionString = this.iothubConnectionString, - RegistryManager = rm, - RemoveDevice = true - }; - } - protected Task ConfigureBootstrapper() { IotHubConnectionStringBuilder builder = @@ -183,7 +250,10 @@ protected async Task VerifyEdgeAgentIsConnectedToIotHub() "$edgeAgent", new CloudToDeviceMethod("ping"), cts.Token); - if (result.Status == 200) break; + if (result.Status == 200) + { + break; + } } catch (Exception e) { @@ -238,20 +308,22 @@ protected async Task VerifyDataOnIoTHub(string moduleId) { using (cts.Token.Register(() => result.TrySetCanceled())) { - eventHubReceiver.SetReceiveHandler(new PartitionReceiveHandler(eventData => - { - eventData.SystemProperties.TryGetValue("iothub-connection-device-id", out object devId); - eventData.SystemProperties.TryGetValue("iothub-connection-module-id", out object modId); - - if (devId != null && devId.ToString().Equals(this.context.Device.Id) && - modId != null && modId.ToString().Equals(moduleId)) - { - result.TrySetResult(true); - return true; - } - - return false; - })); + eventHubReceiver.SetReceiveHandler( + new PartitionReceiveHandler( + eventData => + { + eventData.SystemProperties.TryGetValue("iothub-connection-device-id", out object devId); + eventData.SystemProperties.TryGetValue("iothub-connection-module-id", out object modId); + + if (devId != null && devId.ToString().Equals(this.context.Device.Id) && + modId != null && modId.ToString().Equals(moduleId)) + { + result.TrySetResult(true); + return true; + } + + return false; + })); await result.Task; } @@ -281,6 +353,7 @@ protected Task RemoveTempSensorFromEdgeDevice() module.Remove(); } } + config.ModulesContent["$edgeAgent"]["properties.desired"] = desired; return this.context.RegistryManager.ApplyConfigurationContentOnDeviceAsync(this.context.Device.Id, config); @@ -317,6 +390,28 @@ protected Task MaybeDeleteEdgeDeviceIdentity() return Task.CompletedTask; } + async Task CreateEdgeDeviceIdentity(RegistryManager rm) + { + var device = new Device(this.deviceId) + { + Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas }, + Capabilities = new DeviceCapabilities() { IotEdge = true } + }; + + IotHubConnectionStringBuilder builder = IotHubConnectionStringBuilder.Create(this.iothubConnectionString); + Console.WriteLine($"Registering device '{device.Id}' on IoT hub '{builder.HostName}'"); + + device = await rm.AddDeviceAsync(device); + + this.context = new DeviceContext + { + Device = device, + IotHubConnectionString = this.iothubConnectionString, + RegistryManager = rm, + RemoveDevice = true + }; + } + string EdgeAgentImage() { return this.BuildImageName("azureiotedge-agent"); @@ -349,9 +444,10 @@ string BuildImageName(string name) Console.WriteLine($"Deployment file used: {f}"); return JObject.Parse(File.ReadAllText(f)).ToString(); }, - () => { + () => + { string deployJsonRegistry = this.credentials.Match( - c => + c => { string jsonRegistry = DeployJsonRegistry; jsonRegistry = Regex.Replace(jsonRegistry, "", c.Address); @@ -359,8 +455,7 @@ string BuildImageName(string name) jsonRegistry = Regex.Replace(jsonRegistry, "", c.Password); return jsonRegistry; }, - () => string.Empty - ); + () => string.Empty); string json = DeployJson; json = Regex.Replace(json, "", edgeAgentImage); @@ -371,83 +466,8 @@ string BuildImageName(string name) return json; }); - return (deployJson, new [] { edgeAgentImage, edgeHubImage, tempSensorImage }); + return (deployJson, new[] { edgeAgentImage, edgeHubImage, tempSensorImage }); } - - const string DeployJson = @" -{ - ""modulesContent"": { - ""$edgeAgent"": { - ""properties.desired"": { - ""schemaVersion"": ""1.0"", - ""runtime"": { - ""type"": ""docker"", - ""settings"": { - ""minDockerVersion"": ""v1.25"", - ""loggingOptions"": """" - } - }, - ""systemModules"": { - ""edgeAgent"": { - ""type"": ""docker"", - ""settings"": { - ""image"": """", - ""createOptions"": """" - } - }, - ""edgeHub"": { - ""type"": ""docker"", - ""status"": ""running"", - ""restartPolicy"": ""always"", - ""settings"": { - ""image"": """", - ""createOptions"": ""{\""HostConfig\"":{\""PortBindings\"":{\""8883/tcp\"":[{\""HostPort\"":\""8883\""}],\""443/tcp\"":[{\""HostPort\"":\""443\""}],\""5671/tcp\"":[{\""HostPort\"":\""5671\""}]}}}"" - }, - ""env"": { - ""OptimizeForPerformance"": { - ""value"": """" - } - }, - } - }, - ""modules"": { - ""tempSensor"": { - ""version"": ""1.0"", - ""type"": ""docker"", - ""status"": ""running"", - ""restartPolicy"": ""always"", - ""settings"": { - ""image"": """", - ""createOptions"": """" - } - } - } - } - }, - ""$edgeHub"": { - ""properties.desired"": { - ""schemaVersion"": ""1.0"", - ""routes"": { - ""route"": ""FROM /* INTO $upstream"" - }, - ""storeAndForwardConfiguration"": { - ""timeToLiveSecs"": 7200 - } - } - } - } -} -"; - - const string DeployJsonRegistry = @" - ,""registryCredentials"": { - ""registry"": { - ""address"": """", - ""username"": """", - ""password"": """" - } - } -"; } public class DeviceContext diff --git a/smoke/IotEdgeQuickstart/details/Iotedgectl.cs b/smoke/IotEdgeQuickstart/details/Iotedgectl.cs index 520da5f8afc..dd168791fd3 100644 --- a/smoke/IotEdgeQuickstart/details/Iotedgectl.cs +++ b/smoke/IotEdgeQuickstart/details/Iotedgectl.cs @@ -55,7 +55,10 @@ public async Task VerifyModuleIsRunning(string name) $"ps --quiet --filter \"name = {name}\"", cts.Token); - if (!string.IsNullOrWhiteSpace(status.FirstOrDefault())) break; + if (!string.IsNullOrWhiteSpace(status.FirstOrDefault())) + { + break; + } errorMessage = "Not found"; } @@ -89,8 +92,7 @@ public async Task Configure(string connectionString, string image, string hostna string registryArgs = this.credentials.Match( c => $"--docker-registries {c.Address} {c.User} {c.Password}", - () => string.Empty - ); + () => string.Empty); await Process.RunAsync( "iotedgectl", diff --git a/smoke/IotEdgeQuickstart/details/IotedgedLinux.cs b/smoke/IotEdgeQuickstart/details/IotedgedLinux.cs index 7a6e72e747a..4d467e4d031 100644 --- a/smoke/IotEdgeQuickstart/details/IotedgedLinux.cs +++ b/smoke/IotEdgeQuickstart/details/IotedgedLinux.cs @@ -15,12 +15,10 @@ public class HttpUris const int ManagementPort = 15580; const int WorkloadPort = 15581; - public string ConnectManagement { get; } - public string ConnectWorkload { get; } - public string ListenManagement { get; } - public string ListenWorkload { get; } - - public HttpUris() : this(GetIpAddress()) {} + public HttpUris() + : this(GetIpAddress()) + { + } public HttpUris(string hostname) { @@ -30,6 +28,14 @@ public HttpUris(string hostname) this.ListenWorkload = $"http://0.0.0.0:{WorkloadPort}"; } + public string ConnectManagement { get; } + + public string ConnectWorkload { get; } + + public string ListenManagement { get; } + + public string ListenWorkload { get; } + static string GetIpAddress() { // TODO: should use an internal IP address--e.g. docker0's address--instead @@ -110,9 +116,12 @@ public async Task VerifyModuleIsRunning(string name) .DefaultIfEmpty("name status") .Single() .Split(null as char[], StringSplitOptions.RemoveEmptyEntries) - .ElementAt(1); // second column is STATUS + .ElementAt(1); // second column is STATUS - if (status == "running") break; + if (status == "running") + { + break; + } errorMessage = "Not found"; } @@ -207,7 +216,6 @@ public async Task Configure(string connectionString, string image, string hostna string result = doc.ToString(); - FileAttributes attr = 0; if (File.Exists(YamlPath)) { @@ -240,7 +248,11 @@ public async Task Start() { await Task.Delay(TimeSpan.FromSeconds(3), cts.Token); string[] result = await Process.RunAsync("bash", "-c \"systemctl --no-pager show iotedge | grep ActiveState=\""); - if (result.First().Split("=").Last() == "active") break; + if (result.First().Split("=").Last() == "active") + { + break; + } + errorMessage = result.First(); } } diff --git a/smoke/IotEdgeQuickstart/details/IotedgedWindows.cs b/smoke/IotEdgeQuickstart/details/IotedgedWindows.cs index daa360f0a66..b6b0279b136 100644 --- a/smoke/IotEdgeQuickstart/details/IotedgedWindows.cs +++ b/smoke/IotEdgeQuickstart/details/IotedgedWindows.cs @@ -103,7 +103,6 @@ public Task Install() { // Windows installation does install + configure in one step. Since we need to connection string // to configure and we don't have that information here, we'll do installation in Configure(). - return Task.CompletedTask; } @@ -130,7 +129,7 @@ await Process.RunAsync( } string args = $". {this.scriptDir}\\IotEdgeSecurityDaemon.ps1; Install-SecurityDaemon -Manual " + - $"-ContainerOs Windows -DeviceConnectionString '{connectionString}' -AgentImage '{image}'"; + $"-ContainerOs Windows -DeviceConnectionString '{connectionString}' -AgentImage '{image}'"; this.proxy.ForEach(proxy => { args += $" -Proxy '{proxy}'"; }); @@ -145,7 +144,7 @@ await Process.RunAsync( { args += $" -Username '{c.User}' -Password (ConvertTo-SecureString '{c.Password}' -AsPlainText -Force)"; } - + // note: ignore hostname for now Console.WriteLine($"Run command to configure: {commandForDebug}"); string[] result = await Process.RunAsync("powershell", args, cts.Token); @@ -162,46 +161,6 @@ await Process.RunAsync( } } - static void UpdateConfigYamlFile(string deviceCaCert, string deviceCaPk, string trustBundleCerts, LogLevel runtimeLogLevel) - { - string config = File.ReadAllText(ConfigYamlFile); - var doc = new YamlDocument(config); - doc.ReplaceOrAdd("agent.env.RuntimeLogLevel", runtimeLogLevel.ToString()); - - if (!string.IsNullOrEmpty(deviceCaCert) && !string.IsNullOrEmpty(deviceCaPk) && !string.IsNullOrEmpty(trustBundleCerts)) - { - doc.ReplaceOrAdd("certificates.device_ca_cert", deviceCaCert); - doc.ReplaceOrAdd("certificates.device_ca_pk", deviceCaPk); - doc.ReplaceOrAdd("certificates.trusted_ca_certs", trustBundleCerts); - } - - FileAttributes attr = 0; - attr = File.GetAttributes(ConfigYamlFile); - File.SetAttributes(ConfigYamlFile, attr & ~FileAttributes.ReadOnly); - - File.WriteAllText(ConfigYamlFile, doc.ToString()); - - if (attr != 0) - { - File.SetAttributes(ConfigYamlFile, attr); - } - } - - static void SetEnvironmentVariable() - { - string config = File.ReadAllText(ConfigYamlFile); - var managementUriRegex = new Regex(@"connect:\s*management_uri:\s*""*(.*)""*"); - Match result = managementUriRegex.Match(config); - - if (result.Groups.Count != 2) - { - throw new Exception("can't find management Uri in config file."); - } - - Console.WriteLine($"Explicitly set environment variable [IOTEDGE_HOST={result.Groups[1].Value}]"); - Environment.SetEnvironmentVariable("IOTEDGE_HOST", result.Groups[1].Value); - } - public async Task Start() { Console.WriteLine("Starting iotedge service."); @@ -280,9 +239,49 @@ public Task Stop() return Task.CompletedTask; } - + public Task Reset() => Task.CompletedTask; + static void UpdateConfigYamlFile(string deviceCaCert, string deviceCaPk, string trustBundleCerts, LogLevel runtimeLogLevel) + { + string config = File.ReadAllText(ConfigYamlFile); + var doc = new YamlDocument(config); + doc.ReplaceOrAdd("agent.env.RuntimeLogLevel", runtimeLogLevel.ToString()); + + if (!string.IsNullOrEmpty(deviceCaCert) && !string.IsNullOrEmpty(deviceCaPk) && !string.IsNullOrEmpty(trustBundleCerts)) + { + doc.ReplaceOrAdd("certificates.device_ca_cert", deviceCaCert); + doc.ReplaceOrAdd("certificates.device_ca_pk", deviceCaPk); + doc.ReplaceOrAdd("certificates.trusted_ca_certs", trustBundleCerts); + } + + FileAttributes attr = 0; + attr = File.GetAttributes(ConfigYamlFile); + File.SetAttributes(ConfigYamlFile, attr & ~FileAttributes.ReadOnly); + + File.WriteAllText(ConfigYamlFile, doc.ToString()); + + if (attr != 0) + { + File.SetAttributes(ConfigYamlFile, attr); + } + } + + static void SetEnvironmentVariable() + { + string config = File.ReadAllText(ConfigYamlFile); + var managementUriRegex = new Regex(@"connect:\s*management_uri:\s*""*(.*)""*"); + Match result = managementUriRegex.Match(config); + + if (result.Groups.Count != 2) + { + throw new Exception("can't find management Uri in config file."); + } + + Console.WriteLine($"Explicitly set environment variable [IOTEDGE_HOST={result.Groups[1].Value}]"); + Environment.SetEnvironmentVariable("IOTEDGE_HOST", result.Groups[1].Value); + } + static void WriteToConsole(string header, string[] result) { Console.WriteLine(header); diff --git a/smoke/IotEdgeQuickstart/details/PartitionReceiveHandler.cs b/smoke/IotEdgeQuickstart/details/PartitionReceiveHandler.cs index 5295b2dbbcb..98e30c9e590 100644 --- a/smoke/IotEdgeQuickstart/details/PartitionReceiveHandler.cs +++ b/smoke/IotEdgeQuickstart/details/PartitionReceiveHandler.cs @@ -9,22 +9,30 @@ namespace IotEdgeQuickstart.Details class PartitionReceiveHandler : IPartitionReceiveHandler { readonly Func onEventReceived; + public PartitionReceiveHandler(Func onEventReceived) { this.onEventReceived = onEventReceived; } + + public int MaxBatchSize { get; set; } + public Task ProcessEventsAsync(IEnumerable events) { if (events != null) { foreach (EventData @event in events) { - if (this.onEventReceived(@event)) break; + if (this.onEventReceived(@event)) + { + break; + } } } + return Task.CompletedTask; } + public Task ProcessErrorAsync(Exception error) => throw error; - public int MaxBatchSize { get; set; } } } diff --git a/smoke/IotEdgeQuickstart/details/RegistryCredentials.cs b/smoke/IotEdgeQuickstart/details/RegistryCredentials.cs index f391cfdb900..e2ed531db4e 100644 --- a/smoke/IotEdgeQuickstart/details/RegistryCredentials.cs +++ b/smoke/IotEdgeQuickstart/details/RegistryCredentials.cs @@ -5,21 +5,32 @@ namespace IotEdgeQuickstart.Details public class RegistryCredentials { - public string Address { get; } - - public string User { get; } - - public string Password { get; } - public RegistryCredentials(string address, string user, string password) { - if (string.IsNullOrEmpty(address)) throw new ArgumentException("address cannot be null or empty"); - if (string.IsNullOrEmpty(user)) throw new ArgumentException("user cannot be null or empty"); - if (string.IsNullOrEmpty(password)) throw new ArgumentException("password cannot be null or empty"); + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentException("address cannot be null or empty"); + } + + if (string.IsNullOrEmpty(user)) + { + throw new ArgumentException("user cannot be null or empty"); + } + + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentException("password cannot be null or empty"); + } this.Address = address; this.User = user; this.Password = password; } + + public string Address { get; } + + public string User { get; } + + public string Password { get; } } } diff --git a/smoke/IotEdgeQuickstart/details/YamlDocument.cs b/smoke/IotEdgeQuickstart/details/YamlDocument.cs index 2eb8a705172..3da09e60ea5 100644 --- a/smoke/IotEdgeQuickstart/details/YamlDocument.cs +++ b/smoke/IotEdgeQuickstart/details/YamlDocument.cs @@ -27,6 +27,7 @@ public void ReplaceOrAdd(string dottedKey, string value) { node.Add(key, new Dictionary()); } + node = (Dictionary)node[key]; } @@ -35,6 +36,7 @@ public void ReplaceOrAdd(string dottedKey, string value) { node.Add(leaf, value); } + node[leaf] = value; } diff --git a/smoke/LeafDevice/LeafDevice.cs b/smoke/LeafDevice/LeafDevice.cs index e21ccc0ab67..30acdb2a2b6 100644 --- a/smoke/LeafDevice/LeafDevice.cs +++ b/smoke/LeafDevice/LeafDevice.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -// ReSharper disable ArrangeThisQualifier namespace LeafDevice { using System; @@ -13,8 +12,8 @@ public LeafDevice( string deviceId, string certificateFileName, string edgeHostName, - bool useWebSockets) : - base(iothubConnectionString, eventhubCompatibleEndpointWithEntityPath, deviceId, certificateFileName, edgeHostName, useWebSockets) + bool useWebSockets) + : base(iothubConnectionString, eventhubCompatibleEndpointWithEntityPath, deviceId, certificateFileName, edgeHostName, useWebSockets) { } @@ -24,21 +23,21 @@ public async Task RunAsync() try { await this.InitializeServerCerts(); - await GetOrCreateDeviceIdentity(); - await ConnectToEdgeAndSendData(); + await this.GetOrCreateDeviceIdentity(); + await this.ConnectToEdgeAndSendData(); await this.VerifyDataOnIoTHub(); await this.VerifyDirectMethod(); } catch (Exception) { Console.WriteLine("** Oops, there was a problem."); - KeepDeviceIdentity(); + this.KeepDeviceIdentity(); throw; } finally { // only remove the identity if we created it; if it already existed in IoT Hub then leave it alone - await MaybeDeleteDeviceIdentity(); + await this.MaybeDeleteDeviceIdentity(); } } } diff --git a/smoke/LeafDevice/LeafDevice.csproj b/smoke/LeafDevice/LeafDevice.csproj index 6840732ebc8..02a7b7abf91 100644 --- a/smoke/LeafDevice/LeafDevice.csproj +++ b/smoke/LeafDevice/LeafDevice.csproj @@ -34,4 +34,11 @@ + + + + + ..\..\stylecop.ruleset + + diff --git a/smoke/LeafDevice/Program.cs b/smoke/LeafDevice/Program.cs index aa5a49d4131..6a64f3f122f 100644 --- a/smoke/LeafDevice/Program.cs +++ b/smoke/LeafDevice/Program.cs @@ -30,14 +30,10 @@ Option Default value --device-id an auto-generated unique identifier --certificate Empty String. --edge-hostname Empty String. -" - )] +")] [HelpOption] class Program { - // ReSharper disable once UnusedMember.Local - static int Main(string[] args) => CommandLineApplication.ExecuteAsync(args).Result; - [Option("-c|--connection-string ", Description = "Device connection string (hub-scoped, e.g. iothubowner)")] public string DeviceConnectionString { get; } = Environment.GetEnvironmentVariable("iothubConnectionString"); @@ -45,27 +41,30 @@ class Program public string EventHubCompatibleEndpointWithEntityPath { get; } = Environment.GetEnvironmentVariable("eventhubCompatibleEndpointWithEntityPath"); [Option("-ct|--certificate ", Description = "Certificate file to be installed on the machine.")] - public string CertificateFileName { get; } = ""; + public string CertificateFileName { get; } = string.Empty; [Option("-d|--device-id", Description = "Leaf device identifier to be registered with IoT Hub")] public string DeviceId { get; } = $"leaf-device--{Guid.NewGuid()}"; [Option("-ed|--edge-hostname", Description = "Leaf device identifier to be registered with IoT Hub")] - public string EdgeHostName { get; } = ""; + public string EdgeHostName { get; } = string.Empty; [Option("--use-web-sockets", CommandOptionType.NoValue, Description = "Use websockets for IoT Hub connections.")] public bool UseWebSockets { get; } = false; + // ReSharper disable once UnusedMember.Local + static int Main(string[] args) => CommandLineApplication.ExecuteAsync(args).Result; + // ReSharper disable once UnusedMember.Local async Task OnExecuteAsync() { try { string connectionString = this.DeviceConnectionString ?? - await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); + await SecretsHelper.GetSecretFromConfigKey("iotHubConnStrKey"); string endpoint = this.EventHubCompatibleEndpointWithEntityPath ?? - await SecretsHelper.GetSecretFromConfigKey("eventHubConnStrKey"); + await SecretsHelper.GetSecretFromConfigKey("eventHubConnStrKey"); var test = new LeafDevice( connectionString, diff --git a/smoke/LeafDevice/details/CustomCertificateValidator.cs b/smoke/LeafDevice/details/CustomCertificateValidator.cs index 454000a51a1..b396323a2c9 100644 --- a/smoke/LeafDevice/details/CustomCertificateValidator.cs +++ b/smoke/LeafDevice/details/CustomCertificateValidator.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -namespace LeafDevice.details +namespace LeafDevice.Details { using System; using System.Collections.Generic; diff --git a/smoke/LeafDevice/details/Details.cs b/smoke/LeafDevice/details/Details.cs index fb564ba7b94..fcda411e3ab 100644 --- a/smoke/LeafDevice/details/Details.cs +++ b/smoke/LeafDevice/details/Details.cs @@ -9,7 +9,7 @@ namespace LeafDevice.Details using System.Text; using System.Threading; using System.Threading.Tasks; - using global::LeafDevice.details; + using global::LeafDevice.Details; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Client; using Microsoft.Azure.Devices.Client.Transport.Mqtt; @@ -42,8 +42,7 @@ protected Details( string deviceId, string certificateFileName, string edgeHostName, - bool useWebSockets - ) + bool useWebSockets) { this.iothubConnectionString = iothubConnectionString; this.eventhubCompatibleEndpointWithEntityPath = eventhubCompatibleEndpointWithEntityPath; @@ -90,8 +89,6 @@ protected Task InitializeServerCerts() return Task.CompletedTask; } - X509Certificate2 GetCertificate() => new X509Certificate2(X509Certificate.CreateFromCertFile(this.certificateFileName)); - protected async Task ConnectToEdgeAndSendData() { IotHubConnectionStringBuilder builder = IotHubConnectionStringBuilder.Create(this.iothubConnectionString); @@ -136,30 +133,6 @@ protected async Task GetOrCreateDeviceIdentity() } } - async Task CreateDeviceIdentity(RegistryManager rm) - { - var device = new Device(this.deviceId) - { - Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas }, - Capabilities = new DeviceCapabilities() { IotEdge = false } - }; - - IotHubConnectionStringBuilder builder = IotHubConnectionStringBuilder.Create(this.iothubConnectionString); - Console.WriteLine($"Registering device '{device.Id}' on IoT hub '{builder.HostName}'"); - - device = await rm.AddDeviceAsync(device); - - this.context = new DeviceContext - { - Device = device, - DeviceClientInstance = Option.None(), - IotHubConnectionString = this.iothubConnectionString, - RegistryManager = rm, - RemoveDevice = true, - MessageGuid = Guid.NewGuid().ToString() - }; - } - protected async Task VerifyDataOnIoTHub() { var builder = new EventHubsConnectionStringBuilder(this.eventhubCompatibleEndpointWithEntityPath); @@ -189,7 +162,7 @@ protected async Task VerifyDataOnIoTHub() eventData.SystemProperties.TryGetValue("iothub-connection-device-id", out object devId); if (devId != null && devId.ToString().Equals(this.context.Device.Id) - && Encoding.UTF8.GetString(eventData.Body).Contains(this.context.MessageGuid)) + && Encoding.UTF8.GetString(eventData.Body).Contains(this.context.MessageGuid)) { result.TrySetResult(true); return true; @@ -206,19 +179,13 @@ protected async Task VerifyDataOnIoTHub() await eventHubClient.CloseAsync(); } - static Task DirectMethod(MethodRequest methodRequest, object userContext) - { - Console.WriteLine($"Leaf device received direct method call...Payload Received: {methodRequest.DataAsJson}"); - return Task.FromResult(new MethodResponse(methodRequest.Data, (int)HttpStatusCode.OK)); - } - protected async Task VerifyDirectMethod() { - //User Service SDK to invoke Direct Method on the device. + // User Service SDK to invoke Direct Method on the device. ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(this.context.IotHubConnectionString, this.serviceClientTransportType); - //Call a direct method + // Call a direct method using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300))) { CloudToDeviceMethod cloudToDeviceMethod = new CloudToDeviceMethod("DirectMethod").SetPayloadJson("{\"TestKey\" : \"TestValue\"}"); @@ -263,6 +230,38 @@ protected Task MaybeDeleteDeviceIdentity() return Task.CompletedTask; } + + static Task DirectMethod(MethodRequest methodRequest, object userContext) + { + Console.WriteLine($"Leaf device received direct method call...Payload Received: {methodRequest.DataAsJson}"); + return Task.FromResult(new MethodResponse(methodRequest.Data, (int)HttpStatusCode.OK)); + } + + X509Certificate2 GetCertificate() => new X509Certificate2(X509Certificate.CreateFromCertFile(this.certificateFileName)); + + async Task CreateDeviceIdentity(RegistryManager rm) + { + var device = new Device(this.deviceId) + { + Authentication = new AuthenticationMechanism() { Type = AuthenticationType.Sas }, + Capabilities = new DeviceCapabilities() { IotEdge = false } + }; + + IotHubConnectionStringBuilder builder = IotHubConnectionStringBuilder.Create(this.iothubConnectionString); + Console.WriteLine($"Registering device '{device.Id}' on IoT hub '{builder.HostName}'"); + + device = await rm.AddDeviceAsync(device); + + this.context = new DeviceContext + { + Device = device, + DeviceClientInstance = Option.None(), + IotHubConnectionString = this.iothubConnectionString, + RegistryManager = rm, + RemoveDevice = true, + MessageGuid = Guid.NewGuid().ToString() + }; + } } public class DeviceContext @@ -277,6 +276,6 @@ public class DeviceContext public bool RemoveDevice { get; set; } - public string MessageGuid { get; set; } //used to identify exactly which message got sent. + public string MessageGuid { get; set; } // used to identify exactly which message got sent. } } diff --git a/smoke/LeafDevice/details/PartitionReceiveHandler.cs b/smoke/LeafDevice/details/PartitionReceiveHandler.cs index eec020b6758..d6df83ee497 100644 --- a/smoke/LeafDevice/details/PartitionReceiveHandler.cs +++ b/smoke/LeafDevice/details/PartitionReceiveHandler.cs @@ -9,22 +9,30 @@ namespace LeafDevice.Details class PartitionReceiveHandler : IPartitionReceiveHandler { readonly Func onEventReceived; + public PartitionReceiveHandler(Func onEventReceived) { this.onEventReceived = onEventReceived; } + + public int MaxBatchSize { get; set; } + public Task ProcessEventsAsync(IEnumerable events) { if (events != null) { foreach (EventData @event in events) { - if (this.onEventReceived(@event)) break; + if (this.onEventReceived(@event)) + { + break; + } } } + return Task.CompletedTask; } + public Task ProcessErrorAsync(Exception error) => throw error; - public int MaxBatchSize { get; set; } } } diff --git a/stylecop.json b/stylecop.json new file mode 100644 index 00000000000..86a5c3c593a --- /dev/null +++ b/stylecop.json @@ -0,0 +1,12 @@ +{ + "$schema": + "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Microsoft", + "copyrightText": + "Copyright (c) {companyName}. All rights reserved.", + "xmlHeader": false + } + } +} \ No newline at end of file diff --git a/stylecop.props b/stylecop.props new file mode 100644 index 00000000000..71c2a8cd1d7 --- /dev/null +++ b/stylecop.props @@ -0,0 +1,8 @@ + + + + all + runtime; build; native; contentfiles; analyzers + + + \ No newline at end of file diff --git a/stylecop.ruleset b/stylecop.ruleset new file mode 100644 index 00000000000..5cb7a821677 --- /dev/null +++ b/stylecop.ruleset @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file